From ba5dac74055c99e57d2268ce3f95530b1cb40808 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 16 Dec 2019 21:23:58 +0100 Subject: [PATCH 01/85] Add update func --- .../lists/__snapshots__/lists.test.ts.snap | 21 +++++++++++++ src/utils/lists/lists.test.ts | 30 +++++++++++++++---- src/utils/lists/lists.ts | 8 +++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/utils/lists/__snapshots__/lists.test.ts.snap b/src/utils/lists/__snapshots__/lists.test.ts.snap index 998d4c2e4..c849a90e0 100644 --- a/src/utils/lists/__snapshots__/lists.test.ts.snap +++ b/src/utils/lists/__snapshots__/lists.test.ts.snap @@ -56,6 +56,27 @@ Array [ ] `; +exports[`Properly calculates output arrays Updates 1`] = ` +Array [ + Object { + "name": "lorem", + "value": 0, + }, + Object { + "name": "ipsum", + "value": 1, + }, + Object { + "name": "dolor", + "value": 2, + }, + Object { + "name": "amet", + "value": 32, + }, +] +`; + exports[`Properly calculates output arrays Updates at index 1`] = ` Array [ "lorem", diff --git a/src/utils/lists/lists.test.ts b/src/utils/lists/lists.test.ts index dc6976b1c..99488dc2f 100644 --- a/src/utils/lists/lists.test.ts +++ b/src/utils/lists/lists.test.ts @@ -6,10 +6,12 @@ import { remove, removeAtIndex, toggle, + update, updateAtIndex } from "./lists"; const initialArray = ["lorem", "ipsum", "dolor"]; +const compare = (a, b) => a === b; describe("Properly calculates output arrays", () => { it("Adds", () => { @@ -20,12 +22,28 @@ describe("Properly calculates output arrays", () => { expect(addAtIndex("sit", initialArray, 2)).toMatchSnapshot(); }); + it("Updates", () => { + expect( + update( + { + name: "amet", + value: 32 + }, + initialArray.map((el, index) => ({ + name: el, + value: index + })), + (a, b) => a.name === b.name + ) + ).toMatchSnapshot(); + }); + it("Updates at index", () => { expect(updateAtIndex("amet", initialArray, 1)).toMatchSnapshot(); }); it("Removes", () => { - expect(remove("ipsum", initialArray, (a, b) => a === b)).toMatchSnapshot(); + expect(remove("ipsum", initialArray, compare)).toMatchSnapshot(); }); it("Removes at index", () => { @@ -33,16 +51,16 @@ describe("Properly calculates output arrays", () => { }); it("Matches", () => { - expect(isSelected("lorem", initialArray, (a, b) => a === b)).toBe(true); - expect(isSelected("sit", initialArray, (a, b) => a === b)).toBe(false); + expect(isSelected("lorem", initialArray, compare)).toBe(true); + expect(isSelected("sit", initialArray, compare)).toBe(false); }); it("Toggles", () => { - expect(toggle("lorem", initialArray, (a, b) => a === b)).toMatchSnapshot(); - expect(toggle("sit", initialArray, (a, b) => a === b)).toMatchSnapshot(); + expect(toggle("lorem", initialArray, compare)).toMatchSnapshot(); + expect(toggle("sit", initialArray, compare)).toMatchSnapshot(); }); it("Moves", () => { - expect(move("lorem", initialArray, (a, b) => a === b, 1)).toMatchSnapshot(); + expect(move("lorem", initialArray, compare, 1)).toMatchSnapshot(); }); }); diff --git a/src/utils/lists/lists.ts b/src/utils/lists/lists.ts index 21005bacc..067fc959c 100644 --- a/src/utils/lists/lists.ts +++ b/src/utils/lists/lists.ts @@ -30,6 +30,14 @@ export function move( return addAtIndex(data, remove(data, list, compare), index); } +export function update( + data: TData, + list: List, + compare: Compare +) { + return add(data, remove(data, list, compare)); +} + export function updateAtIndex( data: TData, list: List, From d58b1046ffc65d2cedfecefffb20f58c4b4d4265 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 17 Dec 2019 14:38:13 +0100 Subject: [PATCH 02/85] Rewrite mock choice provider to hooks --- src/storybook/mock.tsx | 168 +++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 80 deletions(-) diff --git a/src/storybook/mock.tsx b/src/storybook/mock.tsx index 0e6b83f0f..7b7d3e839 100644 --- a/src/storybook/mock.tsx +++ b/src/storybook/mock.tsx @@ -1,91 +1,99 @@ import React from "react"; import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent"; +import { FetchMoreProps } from "@saleor/types"; interface ChoiceProviderProps { - children: (props: { - choices: SingleAutocompleteChoiceType[]; - hasMore: boolean; - loading: boolean; - fetchChoices: (value: string) => void; - fetchMore: () => void; - }) => React.ReactElement; + children: ( + props: FetchMoreProps & { + choices: SingleAutocompleteChoiceType[]; + fetchChoices: (value: string) => void; + } + ) => React.ReactElement; choices: SingleAutocompleteChoiceType[]; } -interface ChoiceProviderState { - choices: SingleAutocompleteChoiceType[]; - filteredChoices: SingleAutocompleteChoiceType[]; - first: number; - loading: boolean; - timeout: any; -} const step = 5; +const loadingTime = 400; -export class ChoiceProvider extends React.Component< - ChoiceProviderProps, - ChoiceProviderState -> { - state = { - choices: [], - filteredChoices: [], - first: step, - loading: false, - timeout: null - }; - - handleChange = (inputValue: string) => { - if (!!this.state.timeout) { - clearTimeout(this.state.timeout); - } - const timeout = setTimeout(() => this.fetchChoices(inputValue), 500); - this.setState({ - loading: true, - timeout - }); - }; - - handleFetchMore = () => { - if (!!this.state.timeout) { - clearTimeout(this.state.timeout); - } - const timeout = setTimeout(this.fetchMore, 500); - this.setState({ - loading: true, - timeout - }); - }; - - fetchMore = () => - this.setState(prevState => ({ - filteredChoices: prevState.choices.slice(0, prevState.first + step), - first: prevState.first + step, - loading: false, - timeout: null - })); - - fetchChoices = (inputValue: string) => { - const choices = this.props.choices.filter( - suggestion => - !inputValue || - suggestion.label.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1 - ); - this.setState({ - choices, - filteredChoices: choices.slice(0, step), - first: step, - loading: false, - timeout: null - }); - }; - - render() { - return this.props.children({ - choices: this.state.filteredChoices, - fetchChoices: this.handleChange, - fetchMore: this.handleFetchMore, - hasMore: this.state.choices.length > this.state.filteredChoices.length, - loading: this.state.loading - }); - } +export interface UseMockChoiceProviderOpts extends FetchMoreProps { + fetchChoices: (value: string) => void; } +export type UseMockChoiceProvider = [ + SingleAutocompleteChoiceType[], + UseMockChoiceProviderOpts +]; +export function useMockChoiceProvider( + choices: SingleAutocompleteChoiceType[] +): UseMockChoiceProvider { + const [filteredChoices, setFilteredChoices] = React.useState( + choices.slice(0, step) + ); + const [loading, setLoading] = React.useState(false); + const [first, setFirst] = React.useState(step); + const timeout = React.useRef(null); + + React.useEffect( + () => () => { + if (timeout.current) { + clearTimeout(timeout.current); + } + }, + [] + ); + + const handleChange = (value: string) => { + if (!!timeout.current) { + clearTimeout(timeout.current); + } + timeout.current = setTimeout(() => fetchChoices(value), loadingTime); + }; + + const fetchChoices = (value: string) => { + const filteredChoices = choices.filter( + suggestion => + !value || + suggestion.label.toLowerCase().indexOf(value.toLowerCase()) !== -1 + ); + + setLoading(true); + + timeout.current = setTimeout(() => { + setFilteredChoices(filteredChoices); + setLoading(false); + setFirst(step); + }, loadingTime); + }; + + const handleFetchMore = () => { + setLoading(true); + + timeout.current = setTimeout(() => { + setFilteredChoices(choices.slice(0, first + step)); + setLoading(false); + setFirst(first + step); + }, loadingTime); + }; + + return [ + filteredChoices, + { + fetchChoices: handleChange, + hasMore: choices.length > filteredChoices.length, + loading, + onFetchMore: handleFetchMore + } + ]; +} + +export const ChoiceProvider: React.FC = ({ + children, + choices +}) => { + const [filteredChoices, opts] = useMockChoiceProvider(choices); + + return children({ + choices: filteredChoices, + ...opts + }); +}; From 87b94f47e120c78faa63d837c56df81e64fca825 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 17 Dec 2019 15:20:54 +0100 Subject: [PATCH 03/85] Refactor filter components --- src/components/Filter/Arrow.tsx | 22 + src/components/Filter/Filter.tsx | 23 +- src/components/Filter/FilterActions.tsx | 7 +- src/components/Filter/FilterContent.tsx | 436 +++++++++++++----- src/components/Filter/reducer.test.ts | 0 src/components/Filter/reducer.ts | 43 ++ src/components/Filter/types.ts | 37 +- src/components/Filter/useFilter.ts | 37 ++ src/components/SearchBar/SearchBar.tsx | 4 +- .../SearchInput.tsx} | 10 +- .../SingleSelectField/SingleSelectField.tsx | 7 +- src/storybook/stories/components/Filter.tsx | 166 +++---- src/theme.ts | 1 + .../TranslationsEntitiesListPage.tsx | 15 +- src/utils/filters/fields.ts | 69 +++ src/utils/filters/filters.ts | 35 +- 16 files changed, 626 insertions(+), 286 deletions(-) create mode 100644 src/components/Filter/Arrow.tsx create mode 100644 src/components/Filter/reducer.test.ts create mode 100644 src/components/Filter/reducer.ts create mode 100644 src/components/Filter/useFilter.ts rename src/components/{Filter/FilterSearch.tsx => SearchBar/SearchInput.tsx} (92%) create mode 100644 src/utils/filters/fields.ts diff --git a/src/components/Filter/Arrow.tsx b/src/components/Filter/Arrow.tsx new file mode 100644 index 000000000..ef4cbe5d8 --- /dev/null +++ b/src/components/Filter/Arrow.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +const Arrow: React.FC> = props => ( + + + +); + +Arrow.displayName = "Arrow"; +export default Arrow; diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index 7b0774307..791a8abac 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -1,6 +1,5 @@ import ButtonBase from "@material-ui/core/ButtonBase"; import Grow from "@material-ui/core/Grow"; -import Paper from "@material-ui/core/Paper"; import Popper from "@material-ui/core/Popper"; import { makeStyles } from "@material-ui/core/styles"; import { fade } from "@material-ui/core/styles/colorManipulator"; @@ -17,7 +16,6 @@ import { FilterContent } from "."; export interface FilterProps { currencySymbol: string; menu: IFilter; - filterLabel: string; onFilterAdd: (filter: FilterContentSubmitData) => void; } @@ -82,7 +80,7 @@ const useStyles = makeStyles( { name: "Filter" } ); const Filter: React.FC = props => { - const { currencySymbol, filterLabel, menu, onFilterAdd } = props; + const { currencySymbol, menu, onFilterAdd } = props; const classes = useStyles(props); const anchor = React.useRef(); @@ -122,17 +120,14 @@ const Filter: React.FC = props => { placement === "bottom" ? "right top" : "right bottom" }} > - - {filterLabel} - { - onFilterAdd(data); - setFilterMenuOpened(false); - }} - /> - + { + onFilterAdd(data); + setFilterMenuOpened(false); + }} + /> )} diff --git a/src/components/Filter/FilterActions.tsx b/src/components/Filter/FilterActions.tsx index 60eee1d97..211b5ba59 100644 --- a/src/components/Filter/FilterActions.tsx +++ b/src/components/Filter/FilterActions.tsx @@ -56,13 +56,10 @@ export interface FilterActionsPropsSearch { export interface FilterActionsPropsFilters { currencySymbol: string; menu: IFilter; - filterLabel: string; onFilterAdd: (filter: FilterContentSubmitData) => void; } -export const FilterActionsOnlySearch: React.FC< - FilterActionsPropsSearch -> = props => { +export const FilterActionsOnlySearch: React.FC = props => { const { onSearchChange, placeholder, search } = props; const classes = useStyles(props); @@ -83,7 +80,6 @@ export type FilterActionsProps = FilterActionsPropsSearch & const FilterActions: React.FC = props => { const { currencySymbol, - filterLabel, menu, onFilterAdd, onSearchChange, @@ -97,7 +93,6 @@ const FilterActions: React.FC = props => { { name: TKeys; - value: string | string[]; + value: string[]; } -export interface FilterContentProps { +export interface FilterContentProps { currencySymbol: string; - filters: IFilter; - onSubmit: (data: FilterContentSubmitData) => void; + filters: IFilter; + onFilterPropertyChange: React.Dispatch>; + onClear: () => void; + onSubmit: () => void; } -function checkFilterValue(value: string | string[]): boolean { - if (typeof value === "string") { - return !!value; - } +function checkFilterValue(value: string[]): boolean { return value.some(v => !!v); } @@ -35,113 +43,333 @@ function getFilterChoices(items: IFilter) { } const useStyles = makeStyles( - { + theme => ({ + actionBar: { + alignItems: "center", + display: "flex", + justifyContent: "space-between", + padding: theme.spacing(1, 3) + }, + andLabel: { + margin: theme.spacing(0, 2) + }, + arrow: { + marginRight: theme.spacing(2) + }, + clear: { + marginRight: theme.spacing(1) + }, + filterFieldBar: { + "&:not(:last-of-type)": { + borderBottom: `1px solid ${theme.palette.divider}` + }, + padding: theme.spacing(1, 2.5) + }, + filterSettings: { + background: fade(theme.palette.primary.main, 0.2), + padding: theme.spacing(2, 3) + }, input: { - padding: "20px 12px 17px" + padding: "12px 0 9px 12px" + }, + inputRange: { + alignItems: "center", + display: "flex" + }, + label: { + fontWeight: 600 + }, + option: { + left: -theme.spacing(0.5), + position: "relative" } - }, + }), { name: "FilterContent" } ); +function getIsFilterMultipleChoices( + intl: IntlShape +): SingleAutocompleteChoiceType[] { + return [ + { + label: intl.formatMessage({ + defaultMessage: "is equal to", + description: "is filter range or value" + }), + value: FilterType.SINGULAR + }, + { + label: intl.formatMessage({ + defaultMessage: "is between", + description: "is filter range or value" + }), + value: FilterType.MULTIPLE + } + ]; +} + const FilterContent: React.FC = ({ currencySymbol, filters, + onClear, + onFilterPropertyChange, onSubmit }) => { const intl = useIntl(); - const [menuValue, setMenuValue] = React.useState(null); - const [filterValue, setFilterValue] = React.useState(""); const classes = useStyles({}); - const activeMenu = menuValue - ? getMenuItemByValue(filters, menuValue) - : undefined; - const menus = menuValue - ? walkToRoot(filters, menuValue).slice(-1) - : undefined; - - const onMenuChange = (event: React.ChangeEvent) => { - setMenuValue(event.target.value); - setFilterValue(""); - }; - return ( - <> - +
{ + event.preventDefault(); + onSubmit(); }} - value={menus ? menus[0].value : menuValue} - placeholder={intl.formatMessage({ - defaultMessage: "Select Filter..." - })} - /> - {menus && - menus.map( - (filterItem, filterItemIndex) => - !isLeaf(filterItem) && ( - - - +
+ + + +
+ + +
+
+
+ {filters + .sort((a, b) => (a.name > b.name ? 1 : -1)) + .map(filterField => ( + +
+ } + label={filterField.label} + onChange={() => + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + active: !filterField.active + } + }, + type: "set-property" + }) } - placeholder={intl.formatMessage({ - defaultMessage: "Select Filter..." - })} /> - - ) - )} - {activeMenu && isLeaf(activeMenu) && ( - <> - - {activeMenu.data.additionalText && ( - {activeMenu.data.additionalText} - )} - setFilterValue(value)} - /> - {checkFilterValue(filterValue) && ( - <> - - - - )} - - )} - +
+ {filterField.active && ( +
+ {[FieldType.date, FieldType.price, FieldType.number].includes( + filterField.type + ) && ( + <> + + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + multiple: + event.target.value === FilterType.MULTIPLE + } + }, + type: "set-property" + }) + } + /> + +
+
+ +
+ {filterField.multiple ? ( + <> + + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: [ + event.target.value, + filterField.value[1] + ] + } + }, + type: "set-property" + }) + } + /> + + + + + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: [ + filterField.value[0], + event.target.value + ] + } + }, + type: "set-property" + }) + } + /> + + ) : ( + + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: [ + event.target.value, + filterField.value[1] + ] + } + }, + type: "set-property" + }) + } + /> + )} +
+ + )} + {filterField.type === FieldType.options && + (filterField.multiple ? ( + filterField.options.map(option => ( +
+ + } + label={option.label} + name={filterField.name} + onChange={() => + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: toggle( + option.value, + filterField.value, + (a, b) => a === b + ) + } + }, + type: "set-property" + }) + } + /> +
+ )) + ) : ( + + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: [event.target.value] + } + }, + type: "set-property" + }) + } + /> + ))} +
+ )} +
+ ))} + + ); }; FilterContent.displayName = "FilterContent"; diff --git a/src/components/Filter/reducer.test.ts b/src/components/Filter/reducer.test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/Filter/reducer.ts b/src/components/Filter/reducer.ts new file mode 100644 index 000000000..0bbf59602 --- /dev/null +++ b/src/components/Filter/reducer.ts @@ -0,0 +1,43 @@ +import { update } from "@saleor/utils/lists"; +import { IFilter, IFilterElementMutableData } from "./types"; + +export type FilterReducerActionType = "clear" | "reset" | "set-property"; +export interface FilterReducerAction { + type: FilterReducerActionType; + payload: Partial<{ + name: T; + update: Partial; + reset: IFilter; + }>; +} + +function setProperty( + prevState: IFilter, + filter: T, + updateData: Partial +): IFilter { + const field = prevState.find(f => f.name === filter); + const updatedField = { + ...field, + ...updateData + }; + + return update(updatedField, prevState, (a, b) => a.name === b.name); +} + +function reduceFilter( + prevState: IFilter, + action: FilterReducerAction +): IFilter { + switch (action.type) { + case "clear": + return prevState; + case "set-property": + return setProperty(prevState, action.payload.name, action.payload.update); + case "reset": + return action.payload.reset; + } + return prevState; +} + +export default reduceFilter; diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index c991801b8..cfa8fb606 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -1,30 +1,33 @@ -import { IMenu, IMenuItem } from "../../utils/menu"; +import { FetchMoreProps } from "@saleor/types"; +import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; export enum FieldType { date, - hidden, number, price, - range, - rangeDate, - rangePrice, - select, + options, text } -export interface FilterChoice { +export interface IFilterElementMutableData { + active: boolean; + multiple: boolean; + options?: MultiAutocompleteChoiceType[]; + value: T[]; +} +export interface IFilterElement + extends Partial, + IFilterElementMutableData { + autocomplete?: boolean; + currencySymbol?: string; label: string; - value: string | boolean; -} - -export interface FilterData { - additionalText?: string; - fieldLabel: string; - options?: FilterChoice[]; + name: T; type: FieldType; - value?: string; } -export type IFilterItem = IMenuItem; +export type IFilter = Array>; -export type IFilter = IMenu; +export enum FilterType { + MULTIPLE = "MULTIPLE", + SINGULAR = "SINGULAR" +} diff --git a/src/components/Filter/useFilter.ts b/src/components/Filter/useFilter.ts new file mode 100644 index 000000000..dff73fdb6 --- /dev/null +++ b/src/components/Filter/useFilter.ts @@ -0,0 +1,37 @@ +import { useReducer, useEffect, Dispatch } from "react"; + +import reduceFilter, { FilterReducerAction } from "./reducer"; +import { IFilter, IFilterElement } from "./types"; + +function createInitialFilter( + initialFilter: IFilter +): IFilter { + return initialFilter; +} + +export type UseFilter = [ + Array>, + Dispatch>, + () => void +]; + +function useFilter(initialFilter: IFilter): UseFilter { + const [data, dispatchFilterAction] = useReducer( + reduceFilter, + createInitialFilter(initialFilter) + ); + + const reset = () => + dispatchFilterAction({ + payload: { + reset: initialFilter + }, + type: "reset" + }); + + useEffect(reset, [initialFilter]); + + return [data, dispatchFilterAction, reset]; +} + +export default useFilter; diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 9e34a024b..15d8d67d8 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -2,8 +2,8 @@ import React from "react"; import { useIntl } from "react-intl"; import { SearchPageProps, TabPageProps } from "@saleor/types"; -import FilterSearch from "../Filter/FilterSearch"; import FilterTabs, { FilterTab } from "../TableFilter"; +import SearchInput from "./SearchInput"; export interface SearchBarProps extends SearchPageProps, TabPageProps { allTabLabel: string; @@ -47,7 +47,7 @@ const SearchBar: React.FC = props => { /> )} - void; @@ -29,11 +29,11 @@ const useStyles = makeStyles( } }), { - name: "FilterSearch" + name: "SearchInput" } ); -const FilterSearch: React.FC = props => { +const SearchInput: React.FC = props => { const { displaySearchAction, initialSearch, @@ -93,5 +93,5 @@ const FilterSearch: React.FC = props => { ); }; -FilterSearch.displayName = "FilterSearch"; -export default FilterSearch; +SearchInput.displayName = "SearchInput"; +export default SearchInput; diff --git a/src/components/SingleSelectField/SingleSelectField.tsx b/src/components/SingleSelectField/SingleSelectField.tsx index 39810f834..87bd69de3 100644 --- a/src/components/SingleSelectField/SingleSelectField.tsx +++ b/src/components/SingleSelectField/SingleSelectField.tsx @@ -8,6 +8,7 @@ import { makeStyles } from "@material-ui/core/styles"; import classNames from "classnames"; import React from "react"; import { FormattedMessage } from "react-intl"; +import { InputProps } from "@material-ui/core/Input"; const useStyles = makeStyles( theme => ({ @@ -38,13 +39,13 @@ interface SingleSelectFieldProps { selectProps?: SelectProps; placeholder?: string; value?: string; + InputProps?: InputProps; onChange(event: any); } export const SingleSelectField: React.FC = props => { const { className, - disabled, error, label, @@ -54,7 +55,8 @@ export const SingleSelectField: React.FC = props => { name, hint, selectProps, - placeholder + placeholder, + InputProps } = props; const classes = useStyles(props); @@ -90,6 +92,7 @@ export const SingleSelectField: React.FC = props => { }} name={name} labelWidth={180} + {...InputProps} /> } {...selectProps} diff --git a/src/storybook/stories/components/Filter.tsx b/src/storybook/stories/components/Filter.tsx index 96cbf23de..2d4bf3b62 100644 --- a/src/storybook/stories/components/Filter.tsx +++ b/src/storybook/stories/components/Filter.tsx @@ -1,124 +1,92 @@ import { storiesOf } from "@storybook/react"; import React from "react"; +import { FilterContent, FilterContentProps } from "@saleor/components/Filter"; import { - FieldType, - FilterContent, - FilterContentProps -} from "@saleor/components/Filter"; -import CardDecorator from "../../CardDecorator"; + createPriceField, + createDateField, + createOptionsField +} from "@saleor/utils/filters/fields"; +import useFilter from "@saleor/components/Filter/useFilter"; import Decorator from "../../Decorator"; const props: FilterContentProps = { currencySymbol: "USD", filters: [ + createPriceField("price", "Price", "USD", { + max: "100.00", + min: "20.00" + }), { - children: [], - data: { - fieldLabel: "Category Name", - type: FieldType.text - }, - label: "Category", - value: "category" + ...createDateField("createdAt", "Created At", { + max: "2019-10-23", + min: "2019-09-09" + }), + active: true }, { - children: [], - data: { - fieldLabel: "Product Type Name", - type: FieldType.text - }, - label: "Product Type", - value: "product-type" + ...createOptionsField("status", "Status", ["val1"], false, [ + { + label: "Value 1", + value: "val1" + }, + { + label: "Value 2", + value: "val2" + }, + { + label: "Value 3", + value: "val3" + } + ]), + active: true }, { - children: [], - data: { - fieldLabel: "Status", - options: [ + ...createOptionsField( + "multiplOptions", + "Multiple Options", + ["val1", "val2"], + true, + [ { - label: "Published", - value: true + label: "Value 1", + value: "val1" }, { - label: "Hidden", - value: false + label: "Value 2", + value: "val2" + }, + { + label: "Value 3", + value: "val3" } - ], - type: FieldType.select - }, - label: "Published", - value: "published" - }, - { - children: [], - data: { - fieldLabel: "Stock", - type: FieldType.range - }, - label: "Stock", - value: "stock" - }, - { - children: [ - { - children: [], - data: { - fieldLabel: "Equal to", - type: FieldType.date - }, - label: "Equal to", - value: "date-equal" - }, - { - children: [], - data: { - fieldLabel: "Range", - type: FieldType.rangeDate - }, - label: "Range", - value: "date-range" - } - ], - data: { - fieldLabel: "Date", - type: FieldType.select - }, - label: "Date", - value: "date" - }, - { - children: [ - { - children: [], - data: { - fieldLabel: "Exactly", - type: FieldType.price - }, - label: "Exactly", - value: "price-exactly" - }, - { - children: [], - data: { - fieldLabel: "Range", - type: FieldType.rangePrice - }, - label: "Range", - value: "price-range" - } - ], - data: { - fieldLabel: "Price", - type: FieldType.select - }, - label: "Price", - value: "price" + ] + ), + active: false } ], + onClear: () => undefined, + onFilterPropertyChange: () => undefined, onSubmit: () => undefined }; +const InteractiveStory: React.FC = () => { + const [data, dispatchFilterActions, clear] = useFilter(props.filters); + + return ( + + ); +}; + storiesOf("Generics / Filter", module) - .addDecorator(CardDecorator) + .addDecorator(storyFn => ( +
{storyFn()}
+ )) .addDecorator(Decorator) - .add("default", () => ); + .add("default", () => ) + .add("interactive", () => ); diff --git a/src/theme.ts b/src/theme.ts index 24530be60..164e9f620 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -330,6 +330,7 @@ export default (colors: IThemeColors): Theme => } } }, + backgroundColor: colors.background.paper, borderColor: colors.input.border, top: 0 } diff --git a/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx b/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx index f5c40b3c2..e726fc962 100644 --- a/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx +++ b/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx @@ -4,9 +4,8 @@ import { IntlShape, useIntl } from "react-intl"; import AppHeader from "@saleor/components/AppHeader"; import Container from "@saleor/components/Container"; -import FilterSearch from "@saleor/components/Filter/FilterSearch"; +import SearchInput from "@saleor/components/SearchBar/SearchInput"; import PageHeader from "@saleor/components/PageHeader"; -// tslint:disable no-submodule-imports import { ShopInfo_shop_languages } from "@saleor/components/Shop/types/ShopInfo"; import FilterTabs, { FilterTab } from "@saleor/components/TableFilter"; import { maybe } from "@saleor/misc"; @@ -88,9 +87,13 @@ const tabs: TranslationsEntitiesListFilterTab[] = [ "productTypes" ]; -const TranslationsEntitiesListPage: React.FC< - TranslationsEntitiesListPageProps -> = ({ filters, language, onBack, children, ...searchProps }) => { +const TranslationsEntitiesListPage: React.FC = ({ + filters, + language, + onBack, + children, + ...searchProps +}) => { const intl = useIntl(); const currentTab = tabs.indexOf(filters.current); @@ -157,7 +160,7 @@ const TranslationsEntitiesListPage: React.FC< onClick={filters.onProductTypesTabClick} /> - ; + +export function createPriceField( + name: T, + label: string, + currencySymbol: string, + defaultValue: MinMax +): IFilterElement { + return { + active: false, + currencySymbol, + label, + multiple: true, + name, + type: FieldType.price, + value: [defaultValue.min, defaultValue.max] + }; +} + +export function createDateField( + name: T, + label: string, + defaultValue: MinMax +): IFilterElement { + return { + active: false, + label, + multiple: true, + name, + type: FieldType.date, + value: [defaultValue.min, defaultValue.max] + }; +} + +export function createNumberField( + name: T, + label: string, + defaultValue: MinMax +): IFilterElement { + return { + active: false, + label, + multiple: true, + name, + type: FieldType.number, + value: [defaultValue.min, defaultValue.max] + }; +} + +export function createOptionsField( + name: T, + label: string, + defaultValue: string[], + multiple: boolean, + options: MultiAutocompleteChoiceType[] +): IFilterElement { + return { + active: false, + label, + multiple, + name, + options, + type: FieldType.options, + value: defaultValue + }; +} diff --git a/src/utils/filters/filters.ts b/src/utils/filters/filters.ts index c620d9fd7..09601718b 100644 --- a/src/utils/filters/filters.ts +++ b/src/utils/filters/filters.ts @@ -5,13 +5,10 @@ function createFilterUtils< function getActiveFilters(params: TQueryParams): TFilters { return Object.keys(params) .filter(key => Object.keys(filters).includes(key)) - .reduce( - (acc, key) => { - acc[key] = params[key]; - return acc; - }, - {} as any - ); + .reduce((acc, key) => { + acc[key] = params[key]; + return acc; + }, {} as any); } function areFiltersApplied(params: TQueryParams): boolean { @@ -24,30 +21,6 @@ function createFilterUtils< }; } -export function valueOrFirst(value: T | T[]): T { - if (Array.isArray(value)) { - return value[0]; - } - - return value; -} - -export function arrayOrValue(value: T | T[]): T[] { - if (Array.isArray(value)) { - return value; - } - - return [value]; -} - -export function arrayOrUndefined(array: T[]): T[] | undefined { - if (array.length === 0) { - return undefined; - } - - return array; -} - export function dedupeFilter(array: T[]): T[] { return Array.from(new Set(array)); } From aafa6b62dce914f43fd584250cda2295beaa4f7e Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 19 Dec 2019 16:54:52 +0100 Subject: [PATCH 04/85] Refactor filter components --- src/components/Filter/Filter.tsx | 18 +- src/components/Filter/FilterActions.tsx | 109 ------- src/components/Filter/FilterContent.tsx | 15 - src/components/Filter/FilterElement.tsx | 229 -------------- src/components/Filter/reducer.ts | 6 +- src/components/Filter/types.ts | 1 - src/components/FilterBar/FilterBar.tsx | 140 +++++---- src/components/SearchBar/SearchBar.tsx | 70 ++++- src/components/SearchBar/SearchInput.tsx | 76 ++--- src/components/TableFilter/FilterChips.tsx | 156 ---------- src/components/TableFilter/index.ts | 1 - .../ProductListFilter/ProductListFilter.tsx | 153 ---------- .../components/ProductListFilter/index.ts | 2 - .../ProductListPage/ProductListPage.tsx | 27 +- src/products/urls.ts | 2 +- .../views/ProductList/ProductList.tsx | 41 ++- .../views/ProductList/filters.test.ts | 70 +---- src/products/views/ProductList/filters.ts | 289 +++++++----------- src/products/views/ProductList/messages.ts | 33 ++ .../TranslationsEntitiesListPage.tsx | 41 ++- src/types.ts | 13 +- src/utils/filters/fields.ts | 4 +- 22 files changed, 408 insertions(+), 1088 deletions(-) delete mode 100644 src/components/Filter/FilterActions.tsx delete mode 100644 src/components/Filter/FilterElement.tsx delete mode 100644 src/components/TableFilter/FilterChips.tsx delete mode 100644 src/products/components/ProductListFilter/ProductListFilter.tsx delete mode 100644 src/products/components/ProductListFilter/index.ts create mode 100644 src/products/views/ProductList/messages.ts diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index 791a8abac..dbe8cd1e4 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -9,14 +9,14 @@ import classNames from "classnames"; import React from "react"; import { FormattedMessage } from "react-intl"; -import { FilterContentSubmitData } from "./FilterContent"; -import { IFilter } from "./types"; +import { IFilter, IFilterElement } from "./types"; +import useFilter from "./useFilter"; import { FilterContent } from "."; -export interface FilterProps { +export interface FilterProps { currencySymbol: string; menu: IFilter; - onFilterAdd: (filter: FilterContentSubmitData) => void; + onFilterAdd: (filter: Array>) => void; } const useStyles = makeStyles( @@ -71,7 +71,8 @@ const useStyles = makeStyles( width: 240 }, popover: { - zIndex: 1 + width: 376, + zIndex: 3 }, rotate: { transform: "rotate(180deg)" @@ -85,6 +86,7 @@ const Filter: React.FC = props => { const anchor = React.useRef(); const [isFilterMenuOpened, setFilterMenuOpened] = React.useState(false); + const [data, dispatch, reset] = useFilter(menu); return (
@@ -122,8 +124,10 @@ const Filter: React.FC = props => { > { + filters={data} + onClear={reset} + onFilterPropertyChange={dispatch} + onSubmit={() => { onFilterAdd(data); setFilterMenuOpened(false); }} diff --git a/src/components/Filter/FilterActions.tsx b/src/components/Filter/FilterActions.tsx deleted file mode 100644 index 211b5ba59..000000000 --- a/src/components/Filter/FilterActions.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { makeStyles } from "@material-ui/core/styles"; -import TextField, { TextFieldProps } from "@material-ui/core/TextField"; -import classNames from "classnames"; -import React from "react"; - -import { FilterContentSubmitData, IFilter } from "../Filter"; -import Filter from "./Filter"; - -const useInputStyles = makeStyles( - { - input: { - padding: "10.5px 12px" - }, - root: { - flex: 1 - } - }, - { name: "FilterActions" } -); - -const Search: React.FC = props => { - const classes = useInputStyles({}); - - return ( - - ); -}; - -const useStyles = makeStyles( - theme => ({ - actionContainer: { - display: "flex", - flexWrap: "wrap", - padding: theme.spacing(1, 3) - }, - searchOnly: { - paddingBottom: theme.spacing(1.5) - } - }), - { - name: "FilterActions" - } -); - -export interface FilterActionsPropsSearch { - placeholder: string; - search: string; - onSearchChange: (event: React.ChangeEvent) => void; -} -export interface FilterActionsPropsFilters { - currencySymbol: string; - menu: IFilter; - onFilterAdd: (filter: FilterContentSubmitData) => void; -} - -export const FilterActionsOnlySearch: React.FC = props => { - const { onSearchChange, placeholder, search } = props; - const classes = useStyles(props); - - return ( -
- -
- ); -}; - -export type FilterActionsProps = FilterActionsPropsSearch & - FilterActionsPropsFilters; -const FilterActions: React.FC = props => { - const { - currencySymbol, - menu, - onFilterAdd, - onSearchChange, - placeholder, - search - } = props; - const classes = useStyles(props); - - return ( -
- - -
- ); -}; - -FilterActions.displayName = "FilterActions"; -export default FilterActions; diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index 79c8e4c9f..8f3c24b21 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -19,10 +19,6 @@ import { IFilter, FieldType, FilterType } from "./types"; import Arrow from "./Arrow"; import { FilterReducerAction } from "./reducer"; -export interface FilterContentSubmitData { - name: TKeys; - value: string[]; -} export interface FilterContentProps { currencySymbol: string; filters: IFilter; @@ -31,17 +27,6 @@ export interface FilterContentProps { onSubmit: () => void; } -function checkFilterValue(value: string[]): boolean { - return value.some(v => !!v); -} - -function getFilterChoices(items: IFilter) { - return items.map(filterItem => ({ - label: filterItem.label, - value: filterItem.value.toString() - })); -} - const useStyles = makeStyles( theme => ({ actionBar: { diff --git a/src/components/Filter/FilterElement.tsx b/src/components/Filter/FilterElement.tsx deleted file mode 100644 index b5cb5c050..000000000 --- a/src/components/Filter/FilterElement.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { makeStyles } from "@material-ui/core/styles"; -import TextField from "@material-ui/core/TextField"; -import Typography from "@material-ui/core/Typography"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -import Calendar from "../../icons/Calendar"; -import FormSpacer from "../FormSpacer"; -import PriceField from "../PriceField"; -import SingleSelectField from "../SingleSelectField"; -import { FieldType, IFilterItem } from "./types"; - -export interface FilterElementProps { - className?: string; - filter: IFilterItem; - value: string | string[]; - onChange: (value: string | string[]) => void; -} - -const useStyles = makeStyles( - { - calendar: { - margin: 8 - }, - input: { - padding: "20px 12px 17px" - } - }, - { name: "FilterElement" } -); - -export interface FilterElementProps { - className?: string; - currencySymbol: string; - filter: IFilterItem; - value: string | string[]; - onChange: (value: string | string[]) => void; -} - -const FilterElement: React.FC = ({ - currencySymbol, - className, - filter, - onChange, - value -}) => { - const intl = useIntl(); - const classes = useStyles({}); - - if (filter.data.type === FieldType.date) { - return ( - onChange(event.target.value)} - value={value} - InputProps={{ - classes: { - input: classes.input - }, - startAdornment: - }} - /> - ); - } else if (filter.data.type === FieldType.rangeDate) { - return ( - <> - - - - onChange([event.target.value, value[1]])} - InputProps={{ - classes: { - input: classes.input - }, - startAdornment: - }} - /> - - - - - onChange([value[0], event.target.value])} - InputProps={{ - classes: { - input: classes.input - }, - startAdornment: - }} - /> - - ); - } else if (filter.data.type === FieldType.range) { - return ( - <> - - - - onChange([event.target.value, value[1]])} - type="number" - InputProps={{ - classes: { - input: classes.input - } - }} - /> - - - - - onChange([value[0], event.target.value])} - type="number" - InputProps={{ - classes: { - input: classes.input - } - }} - /> - - ); - } else if (filter.data.type === FieldType.rangePrice) { - return ( - <> - - - - onChange([event.target.value, value[1]])} - InputProps={{ - classes: { - input: classes.input - } - }} - /> - - - - - onChange([value[0], event.target.value])} - InputProps={{ - classes: { - input: classes.input - } - }} - /> - - ); - } else if (filter.data.type === FieldType.select) { - return ( - ({ - ...option, - value: option.value.toString() - }))} - selectProps={{ - className, - inputProps: { - className: classes.input - } - }} - value={value as string} - placeholder={intl.formatMessage({ - defaultMessage: "Select Filter..." - })} - onChange={event => onChange(event.target.value)} - /> - ); - } else if (filter.data.type === FieldType.price) { - return ( - onChange(event.target.value)} - InputProps={{ - classes: { - input: !filter.data.fieldLabel && classes.input - } - }} - value={value as string} - /> - ); - } else if (filter.data.type === FieldType.hidden) { - onChange(filter.data.value); - return ; - } - return ( - onChange(event.target.value)} - value={value as string} - /> - ); -}; -FilterElement.displayName = "FilterElement"; -export default FilterElement; diff --git a/src/components/Filter/reducer.ts b/src/components/Filter/reducer.ts index 0bbf59602..a36a09050 100644 --- a/src/components/Filter/reducer.ts +++ b/src/components/Filter/reducer.ts @@ -30,14 +30,14 @@ function reduceFilter( action: FilterReducerAction ): IFilter { switch (action.type) { - case "clear": - return prevState; case "set-property": return setProperty(prevState, action.payload.name, action.payload.update); case "reset": return action.payload.reset; + + default: + return prevState; } - return prevState; } export default reduceFilter; diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index cfa8fb606..1f8b4d7ea 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -19,7 +19,6 @@ export interface IFilterElement extends Partial, IFilterElementMutableData { autocomplete?: boolean; - currencySymbol?: string; label: string; name: T; type: FieldType; diff --git a/src/components/FilterBar/FilterBar.tsx b/src/components/FilterBar/FilterBar.tsx index f4a71b815..54ed13792 100644 --- a/src/components/FilterBar/FilterBar.tsx +++ b/src/components/FilterBar/FilterBar.tsx @@ -1,37 +1,66 @@ import React from "react"; -import { useIntl } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; +import makeStyles from "@material-ui/core/styles/makeStyles"; import { FilterProps } from "../../types"; -import Debounce from "../Debounce"; import { IFilter } from "../Filter/types"; -import FilterTabs, { FilterChips, FilterTab } from "../TableFilter"; +import FilterTabs, { FilterTab } from "../TableFilter"; +import { SearchBarProps } from "../SearchBar"; +import SearchInput from "../SearchBar/SearchInput"; +import Filter from "../Filter"; +import Link from "../Link"; +import Hr from "../Hr"; -export interface FilterBarProps extends FilterProps { - filterMenu: IFilter; +export interface FilterBarProps + extends FilterProps, + SearchBarProps { + filterStructure: IFilter; } -const FilterBar: React.FC = ({ - allTabLabel, - currencySymbol, - filterLabel, - filtersList, - filterMenu, - currentTab, - initialSearch, - searchPlaceholder, - tabs, - onAll, - onSearchChange, - onFilterAdd, - onTabChange, - onTabDelete, - onTabSave -}) => { +const useStyles = makeStyles( + theme => ({ + root: { + display: "flex", + flexWrap: "wrap", + padding: theme.spacing(1, 3) + }, + tabActions: { + borderBottom: `1px solid ${theme.palette.divider}`, + padding: theme.spacing(1, 3, 2), + textAlign: "right" + } + }), + { + name: "FilterBar" + } +); + +const FilterBar: React.FC = props => { + const { + allTabLabel, + currencySymbol, + filterStructure, + currentTab, + initialSearch, + searchPlaceholder, + tabs, + onAll, + onSearchChange, + onFilterChange, + onTabChange, + onTabDelete, + onTabSave + } = props; + + const classes = useStyles(props); const intl = useIntl(); - const [search, setSearch] = React.useState(initialSearch); - React.useEffect(() => setSearch(initialSearch), [currentTab, initialSearch]); const isCustom = currentTab === tabs.length + 1; + const displayTabAction = isCustom + ? "save" + : currentTab === 0 + ? null + : "delete"; return ( <> @@ -53,34 +82,41 @@ const FilterBar: React.FC = ({ /> )} - - {debounceSearchChange => { - const handleSearchChange = (event: React.ChangeEvent) => { - const value = event.target.value; - setSearch(value); - debounceSearchChange(value); - }; - - return ( - - ); - }} - +
+ + +
+ {displayTabAction === null ? ( +
+ ) : ( +
+ {displayTabAction === "save" ? ( + + + + ) : ( + displayTabAction === "delete" && ( + + + + ) + )} +
+ )} ); }; diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 15d8d67d8..9636c5ac0 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -1,8 +1,11 @@ import React from "react"; -import { useIntl } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; +import makeStyles from "@material-ui/core/styles/makeStyles"; import { SearchPageProps, TabPageProps } from "@saleor/types"; import FilterTabs, { FilterTab } from "../TableFilter"; +import Link from "../Link"; +import Hr from "../Hr"; import SearchInput from "./SearchInput"; export interface SearchBarProps extends SearchPageProps, TabPageProps { @@ -10,6 +13,24 @@ export interface SearchBarProps extends SearchPageProps, TabPageProps { searchPlaceholder: string; } +const useStyles = makeStyles( + theme => ({ + root: { + display: "flex", + flexWrap: "wrap", + padding: theme.spacing(1, 3) + }, + tabActions: { + borderBottom: `1px solid ${theme.palette.divider}`, + padding: theme.spacing(1, 3, 2), + textAlign: "right" + } + }), + { + name: "SearchBar" + } +); + const SearchBar: React.FC = props => { const { allTabLabel, @@ -23,9 +44,16 @@ const SearchBar: React.FC = props => { onTabDelete, onTabSave } = props; + + const classes = useStyles(props); const intl = useIntl(); const isCustom = currentTab === tabs.length + 1; + const displayTabAction = isCustom + ? "save" + : currentTab === 0 + ? null + : "delete"; return ( <> @@ -47,16 +75,36 @@ const SearchBar: React.FC = props => { /> )} - +
+ +
+ {displayTabAction === null ? ( +
+ ) : ( +
+ {displayTabAction === "save" ? ( + + + + ) : ( + displayTabAction === "delete" && ( + + + + ) + )} +
+ )} ); }; diff --git a/src/components/SearchBar/SearchInput.tsx b/src/components/SearchBar/SearchInput.tsx index 4816a8942..5d8b38e3e 100644 --- a/src/components/SearchBar/SearchInput.tsx +++ b/src/components/SearchBar/SearchInput.tsx @@ -1,47 +1,31 @@ import { makeStyles } from "@material-ui/core/styles"; import React from "react"; -import { FormattedMessage } from "react-intl"; +import TextField from "@material-ui/core/TextField"; import { SearchPageProps } from "../../types"; import Debounce from "../Debounce"; -import { FilterActionsOnlySearch } from "../Filter/FilterActions"; -import Hr from "../Hr"; -import Link from "../Link"; export interface SearchInputProps extends SearchPageProps { - displaySearchAction: "save" | "delete" | null; - searchPlaceholder: string; - onSearchDelete?: () => void; - onSearchSave?: () => void; + placeholder: string; } const useStyles = makeStyles( - theme => ({ - tabAction: { - display: "inline-block" + { + input: { + padding: "10.5px 12px" }, - tabActionContainer: { - borderBottom: `1px solid ${theme.palette.divider}`, - display: "flex", - justifyContent: "flex-end", - marginTop: theme.spacing(), - padding: theme.spacing(0, 1, 3, 1) + root: { + flex: 1 } - }), + }, { name: "SearchInput" } ); const SearchInput: React.FC = props => { - const { - displaySearchAction, - initialSearch, - onSearchChange, - onSearchDelete, - onSearchSave, - searchPlaceholder - } = props; + const { initialSearch, onSearchChange, placeholder } = props; + const classes = useStyles(props); const [search, setSearch] = React.useState(initialSearch); React.useEffect(() => setSearch(initialSearch), [initialSearch]); @@ -56,37 +40,15 @@ const SearchInput: React.FC = props => { }; return ( - <> - - {!!displaySearchAction ? ( -
-
- {displaySearchAction === "save" ? ( - - - - ) : ( - - - - )} -
-
- ) : ( -
- )} - + ); }} diff --git a/src/components/TableFilter/FilterChips.tsx b/src/components/TableFilter/FilterChips.tsx deleted file mode 100644 index c71d71984..000000000 --- a/src/components/TableFilter/FilterChips.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import ButtonBase from "@material-ui/core/ButtonBase"; -import { makeStyles, useTheme } from "@material-ui/core/styles"; -import { fade } from "@material-ui/core/styles/colorManipulator"; -import Typography from "@material-ui/core/Typography"; -import ClearIcon from "@material-ui/icons/Clear"; -import React from "react"; -import { FormattedMessage } from "react-intl"; - -import FilterActions, { FilterActionsProps } from "../Filter/FilterActions"; -import Hr from "../Hr"; -import Link from "../Link"; - -export interface Filter { - label: string; - onClick: () => void; -} - -const useStyles = makeStyles( - theme => ({ - filterButton: { - alignItems: "center", - backgroundColor: fade(theme.palette.primary.main, 0.8), - borderRadius: "19px", - display: "flex", - height: "38px", - justifyContent: "space-around", - margin: theme.spacing(0, 1, 2), - marginLeft: 0, - padding: theme.spacing(0, 2) - }, - filterChipContainer: { - display: "flex", - flex: 1, - flexWrap: "wrap", - paddingTop: theme.spacing(2) - }, - filterContainer: { - "& a": { - paddingBottom: 10, - paddingTop: theme.spacing(1) - }, - borderBottom: `1px solid ${theme.palette.divider}`, - display: "flex", - marginTop: -theme.spacing(1), - padding: theme.spacing(0, 2) - }, - filterIcon: { - color: theme.palette.common.white, - height: 16, - width: 16 - }, - filterIconContainer: { - WebkitAppearance: "none", - background: "transparent", - border: "none", - borderRadius: "100%", - cursor: "pointer", - height: 32, - marginRight: -13, - padding: 8, - width: 32 - }, - filterLabel: { - marginBottom: theme.spacing(1) - }, - filterText: { - color: theme.palette.common.white, - fontSize: 14, - fontWeight: 400 as 400, - lineHeight: "38px" - } - }), - { - name: "FilterChips" - } -); - -interface FilterChipProps extends FilterActionsProps { - displayTabAction: "save" | "delete" | null; - filtersList: Filter[]; - search: string; - isCustomSearch: boolean; - onFilterDelete: () => void; - onFilterSave: () => void; -} - -export const FilterChips: React.FC = ({ - currencySymbol, - displayTabAction, - filtersList, - menu, - filterLabel, - placeholder, - onSearchChange, - search, - onFilterAdd, - onFilterSave, - onFilterDelete -}) => { - const theme = useTheme(); - const classes = useStyles({ theme }); - - return ( - <> - - {search || (filtersList && filtersList.length > 0) ? ( -
-
- {filtersList.map(filter => ( -
- - {filter.label} - - - - -
- ))} -
- {displayTabAction === "save" ? ( - - - - ) : ( - displayTabAction === "delete" && ( - - - - ) - )} -
- ) : ( -
- )} - - ); -}; - -export default FilterChips; diff --git a/src/components/TableFilter/index.ts b/src/components/TableFilter/index.ts index 5cb8830ab..c1bffcf95 100644 --- a/src/components/TableFilter/index.ts +++ b/src/components/TableFilter/index.ts @@ -1,4 +1,3 @@ export { default } from "./FilterTabs"; export * from "./FilterTabs"; export * from "./FilterTab"; -export * from "./FilterChips"; diff --git a/src/products/components/ProductListFilter/ProductListFilter.tsx b/src/products/components/ProductListFilter/ProductListFilter.tsx deleted file mode 100644 index 52074f5ed..000000000 --- a/src/products/components/ProductListFilter/ProductListFilter.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import React from "react"; -import { useIntl } from "react-intl"; - -import { FieldType, IFilter } from "@saleor/components/Filter"; -import FilterBar from "@saleor/components/FilterBar"; -import { FilterProps } from "@saleor/types"; -import { StockAvailability } from "@saleor/types/globalTypes"; - -type ProductListFilterProps = Omit< - FilterProps, - "allTabLabel" | "filterLabel" | "searchPlaceholder" ->; - -export enum ProductFilterKeys { - published = "published", - price = "price", - priceEqual = "priceEqual", - priceRange = "priceRange", - stock = "stock" -} - -const ProductListFilter: React.FC = props => { - const intl = useIntl(); - - const filterMenu: IFilter = [ - { - children: [], - data: { - additionalText: intl.formatMessage({ - defaultMessage: "is set as", - description: "product status is set as" - }), - fieldLabel: intl.formatMessage({ - defaultMessage: "Status", - description: "product status" - }), - options: [ - { - label: intl.formatMessage({ - defaultMessage: "Visible", - description: "product is visible" - }), - value: true - }, - { - label: intl.formatMessage({ - defaultMessage: "Hidden", - description: "product is hidden" - }), - value: false - } - ], - type: FieldType.select - }, - label: intl.formatMessage({ - defaultMessage: "Visibility", - description: "product visibility" - }), - value: ProductFilterKeys.published - }, - { - children: [], - data: { - fieldLabel: intl.formatMessage({ - defaultMessage: "Stock quantity" - }), - options: [ - { - label: intl.formatMessage({ - defaultMessage: "Available", - description: "product status" - }), - value: StockAvailability.IN_STOCK - }, - { - label: intl.formatMessage({ - defaultMessage: "Out Of Stock", - description: "product status" - }), - value: StockAvailability.OUT_OF_STOCK - } - ], - type: FieldType.select - }, - label: intl.formatMessage({ - defaultMessage: "Stock", - description: "product stock" - }), - value: ProductFilterKeys.stock - }, - { - children: [ - { - children: [], - data: { - additionalText: intl.formatMessage({ - defaultMessage: "equals", - description: "product price" - }), - fieldLabel: null, - type: FieldType.price - }, - label: intl.formatMessage({ - defaultMessage: "Specific Price" - }), - value: ProductFilterKeys.priceEqual - }, - { - children: [], - data: { - fieldLabel: intl.formatMessage({ - defaultMessage: "Range" - }), - type: FieldType.rangePrice - }, - label: intl.formatMessage({ - defaultMessage: "Range" - }), - value: ProductFilterKeys.priceRange - } - ], - data: { - fieldLabel: intl.formatMessage({ - defaultMessage: "Price" - }), - type: FieldType.range - }, - label: intl.formatMessage({ - defaultMessage: "Price" - }), - value: ProductFilterKeys.price - } - ]; - - return ( - - ); -}; -ProductListFilter.displayName = "ProductListFilter"; -export default ProductListFilter; diff --git a/src/products/components/ProductListFilter/index.ts b/src/products/components/ProductListFilter/index.ts deleted file mode 100644 index c251bdbef..000000000 --- a/src/products/components/ProductListFilter/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./ProductListFilter"; -export * from "./ProductListFilter"; diff --git a/src/products/components/ProductListPage/ProductListPage.tsx b/src/products/components/ProductListPage/ProductListPage.tsx index 6ebc13096..4c58bfff0 100644 --- a/src/products/components/ProductListPage/ProductListPage.tsx +++ b/src/products/components/ProductListPage/ProductListPage.tsx @@ -23,9 +23,13 @@ import { PageListProps, SortPage } from "@saleor/types"; +import FilterBar from "@saleor/components/FilterBar"; +import { + createFilterStructure, + ProductFilterKeys +} from "@saleor/products/views/ProductList/filters"; import { ProductListUrlSortField } from "../../urls"; import ProductList from "../ProductList"; -import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter"; export interface ProductListPageProps extends PageListProps, @@ -55,7 +59,6 @@ export const ProductListPage: React.FC = props => { currencySymbol, currentTab, defaultSettings, - filtersList, gridAttributes, availableInGridAttributes, hasMore, @@ -67,8 +70,8 @@ export const ProductListPage: React.FC = props => { onAdd, onAll, onFetchMore, + onFilterChange, onSearchChange, - onFilterAdd, onTabChange, onTabDelete, onTabSave, @@ -81,6 +84,8 @@ export const ProductListPage: React.FC = props => { const handleSave = (columns: ProductListColumns[]) => onUpdateListSettings("columns", columns); + const filterStructure = createFilterStructure(intl); + const columns: ColumnPickerChoice[] = [ { label: intl.formatMessage({ @@ -140,18 +145,28 @@ export const ProductListPage: React.FC = props => { - ; diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 5b826e5e2..1a0aafb88 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -13,7 +13,6 @@ import SaveFilterTabDialog, { import { defaultListSettings, ProductListColumns } from "@saleor/config"; import useBulkActions from "@saleor/hooks/useBulkActions"; import useListSettings from "@saleor/hooks/useListSettings"; -import useLocale from "@saleor/hooks/useLocale"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import usePaginator, { @@ -26,6 +25,7 @@ import { ProductListVariables } from "@saleor/products/types/ProductList"; import { ListViews } from "@saleor/types"; import { getSortUrlVariables } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { IFilter } from "@saleor/components/Filter"; import ProductListPage from "../../components/ProductListPage"; import { TypedProductBulkDeleteMutation, @@ -48,13 +48,13 @@ import { } from "../../urls"; import { areFiltersApplied, - createFilter, - createFilterChips, deleteFilterTab, getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + ProductFilterKeys, + createFilterQueryParams } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -63,7 +63,6 @@ interface ProductListProps { } export const ProductList: React.FC = ({ params }) => { - const { locale } = useLocale(); const navigate = useNavigator(); const notify = useNotifier(); const paginate = usePaginator(); @@ -108,17 +107,28 @@ export const ProductList: React.FC = ({ params }) => { navigate(productListUrl(filters)); }; - const changeFilterField = (filter: ProductListUrlFilters) => { + const changeFilterField = (filter: IFilter) => { reset(); navigate( productListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...createFilterQueryParams(filter), activeTab: undefined }) ); }; + const handleSearchChange = (query: string) => { + reset(); + navigate( + productListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const handleTabChange = (tab: number) => { reset(); navigate( @@ -241,15 +251,6 @@ export const ProductList: React.FC = ({ params }) => { .hasNextPage, false )} - filtersList={createFilterChips( - params, - { - currencySymbol, - locale - }, - changeFilterField, - intl - )} onAdd={() => navigate(productAddUrl)} disabled={loading} products={maybe(() => @@ -337,10 +338,8 @@ export const ProductList: React.FC = ({ params }) => { selected={listElements.length} toggle={toggle} toggleAll={toggleAll} - onSearchChange={query => changeFilterField({ query })} - onFilterAdd={filter => - changeFilterField(createFilter(filter)) - } + onSearchChange={handleSearchChange} + onFilterChange={filter => changeFilterField(filter)} onTabSave={() => openModal("save-search")} onTabDelete={() => openModal("delete-search")} onTabChange={handleTabChange} diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts index cdb06af94..83b9a8cbb 100644 --- a/src/products/views/ProductList/filters.test.ts +++ b/src/products/views/ProductList/filters.test.ts @@ -1,76 +1,12 @@ -import { createIntl } from "react-intl"; - -import { ProductFilterKeys } from "@saleor/products/components/ProductListFilter"; import { StockAvailability } from "@saleor/types/globalTypes"; -import { createFilter, createFilterChips, getFilterVariables } from "./filters"; - -const mockIntl = createIntl({ - locale: "en" -}); - -describe("Create filter object", () => { - it("with price", () => { - const filter = createFilter({ - name: ProductFilterKeys.priceEqual, - value: "10" - }); - - expect(filter).toMatchSnapshot(); - }); - - it("with price range", () => { - const filter = createFilter({ - name: ProductFilterKeys.priceEqual, - value: ["10", "20"] - }); - - expect(filter).toMatchSnapshot(); - }); - - it("with publication status", () => { - const filter = createFilter({ - name: ProductFilterKeys.published, - value: "false" - }); - - expect(filter).toMatchSnapshot(); - }); - - it("with stock status", () => { - const filter = createFilter({ - name: ProductFilterKeys.stock, - value: StockAvailability.OUT_OF_STOCK - }); - - expect(filter).toMatchSnapshot(); - }); -}); - -test("Crate filter chips", () => { - const chips = createFilterChips( - { - isPublished: "true", - priceFrom: "10", - priceTo: "20", - status: StockAvailability.IN_STOCK - }, - { - currencySymbol: "USD", - locale: "en" - }, - jest.fn(), - mockIntl as any - ); - - expect(chips).toMatchSnapshot(); -}); +import { getFilterVariables } from "./filters"; test("Get filter variables", () => { const filter = getFilterVariables({ - isPublished: "true", priceFrom: "10", priceTo: "20", - status: StockAvailability.IN_STOCK + status: "true", + stockStatus: StockAvailability.IN_STOCK }); expect(filter).toMatchSnapshot(); diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 09896b1a7..21cc8ff50 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -1,7 +1,11 @@ -import { defineMessages, IntlShape } from "react-intl"; +import { IntlShape } from "react-intl"; -import { FilterContentSubmitData } from "../../../components/Filter"; -import { Filter } from "../../../components/TableFilter"; +import { findInEnum, maybe } from "@saleor/misc"; +import { + createOptionsField, + createPriceField +} from "@saleor/utils/filters/fields"; +import { IFilterElement, IFilter } from "../../../components/Filter"; import { ProductFilterInput, StockAvailability @@ -10,204 +14,141 @@ import { createFilterTabUtils, createFilterUtils } from "../../../utils/filters"; -import { ProductFilterKeys } from "../../components/ProductListFilter"; import { ProductListUrlFilters, ProductListUrlFiltersEnum, ProductListUrlQueryParams } from "../../urls"; +import messages from "./messages"; export const PRODUCT_FILTERS_KEY = "productFilters"; +export enum ProductFilterKeys { + status = "status", + price = "price", + stock = "stock" +} + +export enum ProductStatus { + PUBLISHED = "published", + HIDDEN = "hidden" +} + +export function createFilterStructure( + intl: IntlShape, + params: ProductListUrlFilters +): IFilter { + return [ + { + ...createOptionsField( + ProductFilterKeys.status, + intl.formatMessage(messages.visibility), + [ProductStatus.PUBLISHED], + false, + [ + { + label: intl.formatMessage(messages.visible), + value: ProductStatus.PUBLISHED + }, + { + label: intl.formatMessage(messages.hidden), + value: ProductStatus.HIDDEN + } + ] + ), + active: maybe(() => params.status !== undefined, false) + }, + { + ...createOptionsField( + ProductFilterKeys.stock, + intl.formatMessage(messages.quantity), + [StockAvailability.IN_STOCK], + false, + [ + { + label: intl.formatMessage(messages.available), + value: StockAvailability.IN_STOCK + }, + { + label: intl.formatMessage(messages.outOfStock), + value: StockAvailability.OUT_OF_STOCK + } + ] + ), + active: maybe(() => params.stockStatus !== undefined, false) + }, + { + ...createPriceField( + ProductFilterKeys.price, + intl.formatMessage(messages.price), + { + max: maybe(() => params.priceTo, "0"), + min: maybe(() => params.priceFrom, "0") + } + ), + active: maybe( + () => + [params.priceFrom, params.priceTo].some(field => field !== undefined), + false + ) + } + ]; +} + export function getFilterVariables( params: ProductListUrlFilters ): ProductFilterInput { return { isPublished: - params.isPublished !== undefined ? params.isPublished === "true" : null, + params.status !== undefined + ? params.status === ProductStatus.PUBLISHED + : null, price: { gte: parseFloat(params.priceFrom), lte: parseFloat(params.priceTo) }, search: params.query, - stockAvailability: StockAvailability[params.status] + stockAvailability: StockAvailability[params.stockStatus] }; } -export function createFilter( - filter: FilterContentSubmitData +export function getFilterQueryParam( + filter: IFilterElement ): ProductListUrlFilters { - const filterName = filter.name; - if (filterName === ProductFilterKeys.priceEqual) { - const value = filter.value as string; - return { - priceFrom: value, - priceTo: value - }; - } else if (filterName === ProductFilterKeys.priceRange) { - const { value } = filter; - return { - priceFrom: value[0], - priceTo: value[1] - }; - } else if (filterName === ProductFilterKeys.published) { - return { - isPublished: filter.value as string - }; - } else if (filterName === ProductFilterKeys.stock) { - const value = filter.value as string; - return { - status: StockAvailability[value] - }; - } -} + const { active, name, value } = filter; -const filterMessages = defineMessages({ - available: { - defaultMessage: "Available", - description: "filter products by stock" - }, - hidden: { - defaultMessage: "Hidden", - description: "filter products by visibility" - }, - outOfStock: { - defaultMessage: "Out of stock", - description: "filter products by stock" - }, - priceFrom: { - defaultMessage: "Price from {price}", - description: "filter by price" - }, - priceIs: { - defaultMessage: "Price is {price}", - description: "filter by price" - }, - priceTo: { - defaultMessage: "Price to {price}", - description: "filter by price" - }, - published: { - defaultMessage: "Published", - description: "filter products by visibility" - } -}); + if (active) { + switch (name) { + case ProductFilterKeys.price: + return { + priceFrom: value[0], + priceTo: value[1] + }; -interface ProductListChipFormatData { - currencySymbol: string; - locale: string; -} -export function createFilterChips( - filters: ProductListUrlFilters, - formatData: ProductListChipFormatData, - onFilterDelete: (filters: ProductListUrlFilters) => void, - intl: IntlShape -): Filter[] { - let filterChips: Filter[] = []; + case ProductFilterKeys.status: + return { + status: ( + findInEnum(value[0], ProductStatus) === ProductStatus.PUBLISHED + ).toString() + }; - if (!!filters.priceFrom || !!filters.priceTo) { - if (filters.priceFrom === filters.priceTo) { - filterChips = [ - ...filterChips, - { - label: intl.formatMessage(filterMessages.priceIs, { - price: parseFloat(filters.priceFrom).toLocaleString( - formatData.locale, - { - currency: formatData.currencySymbol, - style: "currency" - } - ) - }), - onClick: () => - onFilterDelete({ - ...filters, - priceFrom: undefined, - priceTo: undefined - }) - } - ]; - } else { - if (!!filters.priceFrom) { - filterChips = [ - ...filterChips, - { - label: intl.formatMessage(filterMessages.priceFrom, { - price: parseFloat(filters.priceFrom).toLocaleString( - formatData.locale, - { - currency: formatData.currencySymbol, - style: "currency" - } - ) - }), - onClick: () => - onFilterDelete({ - ...filters, - priceFrom: undefined - }) - } - ]; - } - - if (!!filters.priceTo) { - filterChips = [ - ...filterChips, - { - label: intl.formatMessage(filterMessages.priceTo, { - price: parseFloat(filters.priceTo).toLocaleString( - formatData.locale, - { - currency: formatData.currencySymbol, - style: "currency" - } - ) - }), - onClick: () => - onFilterDelete({ - ...filters, - priceTo: undefined - }) - } - ]; - } + case ProductFilterKeys.stock: + return { + status: findInEnum(value[0], StockAvailability) + }; } } - - if (!!filters.status) { - filterChips = [ - ...filterChips, - { - label: - filters.status === StockAvailability.IN_STOCK.toString() - ? intl.formatMessage(filterMessages.available) - : intl.formatMessage(filterMessages.outOfStock), - onClick: () => - onFilterDelete({ - ...filters, - status: undefined - }) - } - ]; - } - - if (!!filters.isPublished) { - filterChips = [ - ...filterChips, - { - label: !!filters.isPublished - ? intl.formatMessage(filterMessages.published) - : intl.formatMessage(filterMessages.hidden), - onClick: () => - onFilterDelete({ - ...filters, - isPublished: undefined - }) - } - ]; - } - - return filterChips; +} +export function createFilterQueryParams( + filter: IFilter +): ProductListUrlFilters { + return filter.reduce( + (acc, filterField) => ({ + ...acc, + ...getFilterQueryParam(filterField) + }), + {} + ); } export const { diff --git a/src/products/views/ProductList/messages.ts b/src/products/views/ProductList/messages.ts new file mode 100644 index 000000000..236534926 --- /dev/null +++ b/src/products/views/ProductList/messages.ts @@ -0,0 +1,33 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + available: { + defaultMessage: "Available", + description: "product status" + }, + hidden: { + defaultMessage: "Hidden", + description: "product is hidden" + }, + outOfStock: { + defaultMessage: "Out Of Stock", + description: "product status" + }, + price: { + defaultMessage: "Price" + }, + quantity: { + defaultMessage: "Stock quantity", + description: "product" + }, + visibility: { + defaultMessage: "Visibility", + description: "product visibility" + }, + visible: { + defaultMessage: "Visible", + description: "product is visible" + } +}); + +export default messages; diff --git a/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx b/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx index e726fc962..e9352f64f 100644 --- a/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx +++ b/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx @@ -1,6 +1,7 @@ import Card from "@material-ui/core/Card"; import React from "react"; import { IntlShape, useIntl } from "react-intl"; +import makeStyles from "@material-ui/core/styles/makeStyles"; import AppHeader from "@saleor/components/AppHeader"; import Container from "@saleor/components/Container"; @@ -30,6 +31,24 @@ export interface TranslationsEntitiesFilters { onProductTypesTabClick: () => void; } +const useStyles = makeStyles( + theme => ({ + root: { + display: "flex", + flexWrap: "wrap", + padding: theme.spacing(1, 3) + }, + tabActions: { + borderBottom: `1px solid ${theme.palette.divider}`, + padding: theme.spacing(1, 3, 2), + textAlign: "right" + } + }), + { + name: "FilterActions" + } +); + export type TranslationsEntitiesListFilterTab = keyof typeof TranslatableEntities; function getSearchPlaceholder( @@ -87,13 +106,10 @@ const tabs: TranslationsEntitiesListFilterTab[] = [ "productTypes" ]; -const TranslationsEntitiesListPage: React.FC = ({ - filters, - language, - onBack, - children, - ...searchProps -}) => { +const TranslationsEntitiesListPage: React.FC = props => { + const { filters, language, onBack, children, ...searchProps } = props; + + const classes = useStyles(props); const intl = useIntl(); const currentTab = tabs.indexOf(filters.current); @@ -160,11 +176,12 @@ const TranslationsEntitiesListPage: React.FC onClick={filters.onProductTypesTabClick} /> - +
+ +
{children}
diff --git a/src/types.ts b/src/types.ts index 4f091c732..cb4ef1d1b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,8 @@ import { MutationResult } from "react-apollo"; import { User_permissions } from "./auth/types/User"; -import { FilterContentSubmitData } from "./components/Filter"; -import { Filter } from "./components/TableFilter"; import { ConfirmButtonTransitionState } from "./components/ConfirmButton"; +import { IFilter } from "./components/Filter"; export interface UserError { field: string; @@ -83,22 +82,20 @@ export interface SearchPageProps { initialSearch: string; onSearchChange: (value: string) => void; } -export interface FilterPageProps +export interface FilterPageProps extends SearchPageProps, TabPageProps { currencySymbol: string; - filtersList: Filter[]; - onFilterAdd: (filter: FilterContentSubmitData) => void; + onFilterChange: (filter: IFilter) => void; } export interface SearchProps { searchPlaceholder: string; } -export interface FilterProps +export interface FilterProps extends FilterPageProps, SearchProps { - allTabLabel: string; - filterLabel: string; + currencySymbol: string; } export interface TabPageProps { diff --git a/src/utils/filters/fields.ts b/src/utils/filters/fields.ts index cc248a93b..a56dfeb8b 100644 --- a/src/utils/filters/fields.ts +++ b/src/utils/filters/fields.ts @@ -6,14 +6,12 @@ type MinMax = Record<"min" | "max", string>; export function createPriceField( name: T, label: string, - currencySymbol: string, defaultValue: MinMax ): IFilterElement { return { active: false, - currencySymbol, label, - multiple: true, + multiple: defaultValue.min !== defaultValue.max, name, type: FieldType.price, value: [defaultValue.min, defaultValue.max] From 32cdb56e6f5515b51e9b221969b083dad687914a Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 20 Dec 2019 11:44:41 +0100 Subject: [PATCH 05/85] Make order filters work --- .../MultiAutocompleteSelectField.stories.tsx | 16 +- .../SingleAutocompleteSelectField.stories.tsx | 16 +- src/misc.ts | 2 +- .../OrderListFilter/OrderListFilter.tsx | 169 ---------- .../components/OrderListFilter/index.ts | 2 - .../OrderListPage/OrderListPage.tsx | 36 ++- src/orders/urls.ts | 4 +- src/orders/views/OrderList/OrderList.tsx | 47 +-- src/orders/views/OrderList/filters.test.ts | 169 +--------- src/orders/views/OrderList/filters.ts | 300 ++++++------------ src/orders/views/OrderList/messages.ts | 29 ++ .../ProductListPage/ProductListPage.tsx | 3 - src/storybook/stories/components/Filter.tsx | 2 +- src/types.ts | 2 +- 14 files changed, 208 insertions(+), 589 deletions(-) delete mode 100644 src/orders/components/OrderListFilter/OrderListFilter.tsx delete mode 100644 src/orders/components/OrderListFilter/index.ts create mode 100644 src/orders/views/OrderList/messages.ts diff --git a/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.stories.tsx b/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.stories.tsx index 533326c51..ff7e70fc0 100644 --- a/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.stories.tsx +++ b/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.stories.tsx @@ -26,18 +26,16 @@ const props: MultiAutocompleteSelectFieldProps = { value: undefined }; -const Story: React.FC< - Partial< - MultiAutocompleteSelectFieldProps & { - enableLoadMore: boolean; - } - > -> = ({ allowCustomValues, enableLoadMore }) => { +const Story: React.FC> = ({ allowCustomValues, enableLoadMore }) => { const { change, data: countries } = useMultiAutocomplete([suggestions[0]]); return ( - {({ choices, fetchChoices, fetchMore, hasMore, loading }) => ( + {({ choices, fetchChoices, onFetchMore, hasMore, loading }) => ( country.value)} loading={loading} hasMore={enableLoadMore ? hasMore : false} - onFetchMore={enableLoadMore ? fetchMore : undefined} + onFetchMore={enableLoadMore ? onFetchMore : undefined} allowCustomValues={allowCustomValues} /> )} diff --git a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.stories.tsx b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.stories.tsx index 1d8468f30..ff1c03143 100644 --- a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.stories.tsx +++ b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.stories.tsx @@ -27,20 +27,18 @@ const props: SingleAutocompleteSelectFieldProps = { value: suggestions[0].value }; -const Story: React.FC< - Partial< - SingleAutocompleteSelectFieldProps & { - enableLoadMore: boolean; - } - > -> = ({ allowCustomValues, emptyOption, enableLoadMore }) => { +const Story: React.FC> = ({ allowCustomValues, emptyOption, enableLoadMore }) => { const [displayValue, setDisplayValue] = React.useState(suggestions[0].label); return (
{({ change, data }) => ( - {({ choices, fetchChoices, fetchMore, hasMore, loading }) => { + {({ choices, fetchChoices, onFetchMore, hasMore, loading }) => { const handleSelect = createSingleAutocompleteSelectHandler( change, setDisplayValue, @@ -58,7 +56,7 @@ const Story: React.FC< onChange={handleSelect} value={data.country} hasMore={enableLoadMore ? hasMore : false} - onFetchMore={enableLoadMore ? fetchMore : undefined} + onFetchMore={enableLoadMore ? onFetchMore : undefined} allowCustomValues={allowCustomValues} emptyOption={emptyOption} /> diff --git a/src/misc.ts b/src/misc.ts index 5df0e61c2..0d5971e8d 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -115,7 +115,7 @@ export const transformPaymentStatus = (status: string, intl: IntlShape) => { } }; -const orderStatusMessages = defineMessages({ +export const orderStatusMessages = defineMessages({ cancelled: { defaultMessage: "Cancelled", description: "order status" diff --git a/src/orders/components/OrderListFilter/OrderListFilter.tsx b/src/orders/components/OrderListFilter/OrderListFilter.tsx deleted file mode 100644 index df399abcf..000000000 --- a/src/orders/components/OrderListFilter/OrderListFilter.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import moment from "moment-timezone"; -import React from "react"; -import { useIntl } from "react-intl"; - -import { DateContext } from "@saleor/components/Date/DateContext"; -import { FieldType, IFilter } from "@saleor/components/Filter"; -import FilterBar from "@saleor/components/FilterBar"; -import TimezoneContext from "@saleor/components/Timezone"; -import { FilterProps } from "../../../types"; -import { OrderStatusFilter } from "../../../types/globalTypes"; - -type OrderListFilterProps = FilterProps; - -export enum OrderFilterKeys { - date = "date", - dateEqual = "dateEqual", - dateRange = "dateRange", - dateLastWeek = "dateLastWeek", - dateLastMonth = "dateLastMonth", - dateLastYear = "dateLastYear", - status = "status" -} - -const OrderListFilter: React.FC = props => { - const date = React.useContext(DateContext); - const tz = React.useContext(TimezoneContext); - const intl = useIntl(); - - const filterMenu: IFilter = [ - { - children: [ - { - children: [], - data: { - fieldLabel: null, - type: FieldType.hidden, - value: (tz ? moment(date).tz(tz) : moment(date)) - .subtract(7, "days") - .toISOString() - .split("T")[0] // Remove timezone - }, - label: intl.formatMessage({ - defaultMessage: "Last 7 Days" - }), - value: OrderFilterKeys.dateLastWeek - }, - { - children: [], - data: { - fieldLabel: null, - type: FieldType.hidden, - value: (tz ? moment(date).tz(tz) : moment(date)) - .subtract(30, "days") - .toISOString() - .split("T")[0] // Remove timezone - }, - label: intl.formatMessage({ - defaultMessage: "Last 30 Days" - }), - value: OrderFilterKeys.dateLastMonth - }, - { - children: [], - data: { - fieldLabel: null, - type: FieldType.hidden, - value: (tz ? moment(date).tz(tz) : moment(date)) - .subtract(1, "years") - .toISOString() - .split("T")[0] // Remove timezone - }, - label: intl.formatMessage({ - defaultMessage: "Last Year" - }), - value: OrderFilterKeys.dateLastYear - }, - { - children: [], - data: { - additionalText: intl.formatMessage({ - defaultMessage: "equals" - }), - fieldLabel: null, - type: FieldType.date - }, - label: intl.formatMessage({ - defaultMessage: "Specific Date" - }), - value: OrderFilterKeys.dateEqual - }, - { - children: [], - data: { - fieldLabel: intl.formatMessage({ - defaultMessage: "Range" - }), - type: FieldType.rangeDate - }, - label: intl.formatMessage({ - defaultMessage: "Range" - }), - value: OrderFilterKeys.dateRange - } - ], - data: { - fieldLabel: intl.formatMessage({ - defaultMessage: "Date" - }), - type: FieldType.select - }, - label: intl.formatMessage({ - defaultMessage: "Date" - }), - value: OrderFilterKeys.date - }, - { - children: [], - data: { - additionalText: intl.formatMessage({ - defaultMessage: "is set as", - description: "date is set as" - }), - fieldLabel: intl.formatMessage({ - defaultMessage: "Status", - description: "order fulfillment status" - }), - options: [ - { - label: intl.formatMessage({ - defaultMessage: "Fulfilled", - description: "order fulfillment status" - }), - value: OrderStatusFilter.FULFILLED.toString() - }, - { - label: intl.formatMessage({ - defaultMessage: "Partially Fulfilled", - description: "order fulfillment status" - }), - value: OrderStatusFilter.PARTIALLY_FULFILLED.toString() - }, - { - label: intl.formatMessage({ - defaultMessage: "Unfulfilled", - description: "order fulfillment status" - }), - value: OrderStatusFilter.UNFULFILLED.toString() - }, - { - label: intl.formatMessage({ - defaultMessage: "Ready to Capture", - description: "order status" - }), - value: OrderStatusFilter.READY_TO_CAPTURE.toString() - } - ], - type: FieldType.select - }, - label: intl.formatMessage({ - defaultMessage: "Order Status" - }), - value: OrderFilterKeys.status - } - ]; - - return ; -}; -OrderListFilter.displayName = "OrderListFilter"; -export default OrderListFilter; diff --git a/src/orders/components/OrderListFilter/index.ts b/src/orders/components/OrderListFilter/index.ts deleted file mode 100644 index 108193863..000000000 --- a/src/orders/components/OrderListFilter/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./OrderListFilter"; -export * from "./OrderListFilter"; diff --git a/src/orders/components/OrderListPage/OrderListPage.tsx b/src/orders/components/OrderListPage/OrderListPage.tsx index 7a72aedbb..151f2ad41 100644 --- a/src/orders/components/OrderListPage/OrderListPage.tsx +++ b/src/orders/components/OrderListPage/OrderListPage.tsx @@ -14,9 +14,13 @@ import { SortPage } from "@saleor/types"; import { OrderListUrlSortField } from "@saleor/orders/urls"; +import { + OrderFilterKeys, + createFilterStructure +} from "@saleor/orders/views/OrderList/filters"; +import FilterBar from "@saleor/components/FilterBar"; import { OrderList_orders_edges_node } from "../../types/OrderList"; import OrderList from "../OrderList"; -import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter"; export interface OrderListPageProps extends PageListProps, @@ -29,13 +33,12 @@ export interface OrderListPageProps const OrderListPage: React.FC = ({ currencySymbol, currentTab, - filtersList, initialSearch, tabs, onAdd, onAll, onSearchChange, - onFilterAdd, + onFilterChange, onTabChange, onTabDelete, onTabSave, @@ -43,6 +46,8 @@ const OrderListPage: React.FC = ({ }) => { const intl = useIntl(); + const filterStructure = createFilterStructure(intl); + return ( @@ -54,28 +59,25 @@ const OrderListPage: React.FC = ({ - diff --git a/src/orders/urls.ts b/src/orders/urls.ts index ffeed351c..145e62c19 100644 --- a/src/orders/urls.ts +++ b/src/orders/urls.ts @@ -17,8 +17,8 @@ const orderSectionUrl = "/orders"; export const orderListPath = orderSectionUrl; export enum OrderListUrlFiltersEnum { - dateFrom = "dateFrom", - dateTo = "dateTo", + createdFrom = "createdFrom", + createdTo = "createdTo", email = "email", payment = "payment", query = "query" diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 40a078ca9..88ea50b18 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -7,7 +7,6 @@ import SaveFilterTabDialog, { SaveFilterTabDialogFormData } from "@saleor/components/SaveFilterTabDialog"; import useBulkActions from "@saleor/hooks/useBulkActions"; -import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useListSettings from "@saleor/hooks/useListSettings"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; @@ -20,6 +19,7 @@ import { ListViews } from "@saleor/types"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { getSortParams } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { IFilter } from "@saleor/components/Filter"; import OrderBulkCancelDialog from "../../components/OrderBulkCancelDialog"; import OrderListPage from "../../components/OrderListPage/OrderListPage"; import { @@ -38,13 +38,13 @@ import { } from "../../urls"; import { areFiltersApplied, - createFilter, - createFilterChips, deleteFilterTab, getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + OrderFilterKeys, + createFilterQueryParams } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -53,7 +53,6 @@ interface OrderListProps { } export const OrderList: React.FC = ({ params }) => { - const formatDate = useDateLocalize(); const navigate = useNavigator(); const notify = useNotifier(); const paginate = usePaginator(); @@ -90,20 +89,36 @@ export const OrderList: React.FC = ({ params }) => { const changeFilters = (filters: OrderListUrlFilters) => { reset(); - navigate(orderListUrl(filters)); + navigate( + orderListUrl({ + ...params, + ...filters + }) + ); }; - const changeFilterField = (filter: OrderListUrlFilters) => { + const changeFilterField = (filter: IFilter) => { reset(); navigate( orderListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...createFilterQueryParams(filter), activeTab: undefined }) ); }; + const handleSearchChange = (query: string) => { + reset(); + navigate( + orderListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const [openModal, closeModal] = createDialogActionHandlers< OrderListUrlDialog, OrderListUrlQueryParams @@ -183,14 +198,6 @@ export const OrderList: React.FC = ({ params }) => { data.orders.edges.map(edge => edge.node))} @@ -221,10 +228,8 @@ export const OrderList: React.FC = ({ params }) => { /> } - onSearchChange={query => changeFilterField({ query })} - onFilterAdd={data => - changeFilterField(createFilter(params, data)) - } + onSearchChange={handleSearchChange} + onFilterChange={filter => changeFilterField(filter)} onTabSave={() => openModal("save-search")} onTabDelete={() => openModal("delete-search")} onTabChange={handleTabChange} diff --git a/src/orders/views/OrderList/filters.test.ts b/src/orders/views/OrderList/filters.test.ts index a8bb1f032..793bbb94c 100644 --- a/src/orders/views/OrderList/filters.test.ts +++ b/src/orders/views/OrderList/filters.test.ts @@ -1,158 +1,17 @@ -import { createIntl } from "react-intl"; +import { OrderStatus } from "@saleor/types/globalTypes"; +import { getFilterVariables } from "./filters"; -import { OrderFilterKeys } from "@saleor/orders/components/OrderListFilter"; -import { OrderStatus, OrderStatusFilter } from "@saleor/types/globalTypes"; -import { createFilter, createFilterChips, getFilterVariables } from "./filters"; +test("Get filter variables", () => { + const filter = getFilterVariables({ + createdFrom: "2019-09-01", + createdTo: "2019-09-10", + email: "email@example.com", + query: "24", + status: [ + OrderStatus.FULFILLED.toString(), + OrderStatus.PARTIALLY_FULFILLED.toString() + ] + }); -const mockIntl = createIntl({ - locale: "en" -}); - -describe("Create filter object", () => { - it("with date", () => { - const filter = createFilter( - {}, - { - name: OrderFilterKeys.dateEqual, - value: "2019-09-01" - } - ); - - expect(filter).toMatchSnapshot(); - }); - - it("with date range", () => { - const filter = createFilter( - {}, - { - name: OrderFilterKeys.dateRange, - value: ["2019-09-01", "2019-09-10"] - } - ); - - expect(filter).toMatchSnapshot(); - }); - - it("with date last week", () => { - const filter = createFilter( - {}, - { - name: OrderFilterKeys.dateLastWeek, - value: "2019-09-01" - } - ); - - expect(filter).toMatchSnapshot(); - }); - - it("with date last month", () => { - const filter = createFilter( - {}, - { - name: OrderFilterKeys.dateLastMonth, - value: "2019-09-01" - } - ); - - expect(filter).toMatchSnapshot(); - }); - - it("with date last year", () => { - const filter = createFilter( - {}, - { - name: OrderFilterKeys.dateLastYear, - value: "2019-09-01" - } - ); - - expect(filter).toMatchSnapshot(); - }); - - it("with fulfillment status", () => { - const filter = createFilter( - {}, - { - name: OrderFilterKeys.status, - value: OrderStatusFilter.PARTIALLY_FULFILLED - } - ); - - expect(filter).toMatchSnapshot(); - }); - - it("with multiple values", () => { - const filter = createFilter( - { - status: [OrderStatusFilter.FULFILLED] - }, - { - name: OrderFilterKeys.status, - value: OrderStatusFilter.PARTIALLY_FULFILLED - } - ); - - expect(filter).toMatchSnapshot(); - }); - - it("with multiple deduped values", () => { - const filter = createFilter( - { - status: [OrderStatusFilter.FULFILLED] - }, - { - name: OrderFilterKeys.status, - value: OrderStatusFilter.FULFILLED - } - ); - - expect(filter).toMatchSnapshot(); - }); -}); - -test("Crate filter chips", () => { - const chips = createFilterChips( - { - dateFrom: "2019-09-01", - dateTo: "2019-09-10", - email: "email@example.com", - status: [OrderStatus.FULFILLED, OrderStatus.PARTIALLY_FULFILLED] - }, - { - formatDate: date => date - }, - jest.fn(), - mockIntl as any - ); - - expect(chips).toMatchSnapshot(); -}); - -describe("Get filter variables", () => { - it("from single status value", () => { - const filter = getFilterVariables({ - dateFrom: "2019-09-01", - dateTo: "2019-09-10", - email: "email@example.com", - query: "24", - status: OrderStatus.FULFILLED.toString() - }); - - expect(filter).toMatchSnapshot(); - }); - - it("from multiple status value", () => { - const filter = getFilterVariables({ - dateFrom: "2019-09-01", - dateTo: "2019-09-10", - email: "email@example.com", - query: "24", - status: [ - OrderStatus.FULFILLED.toString(), - OrderStatus.PARTIALLY_FULFILLED.toString() - ] - }); - - expect(filter).toMatchSnapshot(); - }); + expect(filter).toMatchSnapshot(); }); diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index c9441161b..614d9432e 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -1,78 +1,92 @@ -import { defineMessages, IntlShape } from "react-intl"; +import { IntlShape } from "react-intl"; -import { findInEnum } from "@saleor/misc"; -import { removeAtIndex } from "@saleor/utils/lists"; -import { FilterContentSubmitData } from "../../../components/Filter"; -import { Filter } from "../../../components/TableFilter"; +import { findInEnum, maybe, orderStatusMessages } from "@saleor/misc"; +import { + createDateField, + createOptionsField +} from "@saleor/utils/filters/fields"; +import { IFilter, IFilterElement } from "../../../components/Filter"; import { OrderFilterInput, - OrderStatusFilter + OrderStatusFilter, + OrderStatus } from "../../../types/globalTypes"; import { - arrayOrUndefined, - arrayOrValue, createFilterTabUtils, createFilterUtils, - dedupeFilter, - valueOrFirst + dedupeFilter } from "../../../utils/filters"; -import { OrderFilterKeys } from "../../components/OrderListFilter"; import { OrderListUrlFilters, OrderListUrlFiltersEnum, OrderListUrlFiltersWithMultipleValuesEnum, OrderListUrlQueryParams } from "../../urls"; +import messages from "./messages"; export const ORDER_FILTERS_KEY = "orderFilters"; -const filterMessages = defineMessages({ - dateFrom: { - defaultMessage: "Date from {date}", - description: "filter by date" - }, - dateIs: { - defaultMessage: "Date is {date}", - description: "filter by date" - }, - dateTo: { - defaultMessage: "Date to {date}", - description: "filter by date" - }, - fulfilled: { - defaultMessage: "Fulfilled", - description: "order status" - }, - partiallyFulfilled: { - defaultMessage: "Partially Fulfilled", - description: "order status" - }, - readyToCapture: { - defaultMessage: "Ready to Capture", - description: "order status" - }, - unfulfilled: { - defaultMessage: "Unfulfilled", - description: "order status" - } -}); +export enum OrderFilterKeys { + created = "created", + status = "status" +} -function getStatusLabel(status: string, intl: IntlShape): string { - switch (status) { - case OrderStatusFilter.FULFILLED.toString(): - return intl.formatMessage(filterMessages.fulfilled); - - case OrderStatusFilter.PARTIALLY_FULFILLED.toString(): - return intl.formatMessage(filterMessages.partiallyFulfilled); - - case OrderStatusFilter.UNFULFILLED.toString(): - return intl.formatMessage(filterMessages.unfulfilled); - - case OrderStatusFilter.READY_TO_CAPTURE.toString(): - return intl.formatMessage(filterMessages.readyToCapture); - } - - return ""; +export function createFilterStructure( + intl: IntlShape, + params: OrderListUrlFilters +): IFilter { + return [ + { + ...createDateField( + OrderFilterKeys.created, + intl.formatMessage(messages.placed), + { + max: maybe(() => params.createdTo, ""), + min: maybe(() => params.createdFrom, "") + } + ), + active: maybe( + () => + [params.createdFrom, params.createdTo].some( + field => field !== undefined + ), + false + ) + }, + { + ...createOptionsField( + OrderFilterKeys.status, + intl.formatMessage(messages.status), + maybe( + () => + dedupeFilter( + params.status.map(status => findInEnum(status, OrderStatusFilter)) + ), + [] + ), + true, + [ + { + label: intl.formatMessage(orderStatusMessages.cancelled), + value: OrderStatusFilter.CANCELED + }, + { + label: intl.formatMessage(orderStatusMessages.fulfilled), + value: OrderStatusFilter.FULFILLED + }, + { + label: intl.formatMessage(orderStatusMessages.partiallyFulfilled), + value: OrderStatusFilter.PARTIALLY_FULFILLED + }, + { + label: intl.formatMessage(orderStatusMessages.unfulfilled), + value: OrderStatusFilter.UNFULFILLED + } + ] + ), + active: maybe(() => params.status !== undefined, false) + } + ]; } export function getFilterVariables( @@ -80,159 +94,47 @@ export function getFilterVariables( ): OrderFilterInput { return { created: { - gte: params.dateFrom, - lte: params.dateTo + gte: params.createdFrom, + lte: params.createdTo }, customer: params.email, search: params.query, - status: Array.isArray(params.status) - ? params.status.map(status => findInEnum(status, OrderStatusFilter)) - : params.status - ? [findInEnum(params.status, OrderStatusFilter)] - : undefined + status: maybe(() => + params.status.map(status => findInEnum(status, OrderStatusFilter)) + ) }; } -export function createFilter( - filter: OrderListUrlFilters, - data: FilterContentSubmitData +export function getFilterQueryParam( + filter: IFilterElement ): OrderListUrlFilters { - const { name: filterName, value } = data; - if (filterName === OrderFilterKeys.dateEqual) { - return { - dateFrom: valueOrFirst(value), - dateTo: valueOrFirst(value) - }; - } else if (filterName === OrderFilterKeys.dateRange) { - return { - dateFrom: value[0], - dateTo: value[1] - }; - } else if ( - [ - OrderFilterKeys.dateLastWeek, - OrderFilterKeys.dateLastMonth, - OrderFilterKeys.dateLastYear - ].includes(filterName) - ) { - return { - dateFrom: valueOrFirst(value), - dateTo: undefined - }; - } else if (filterName === OrderFilterKeys.status) { - return { - status: dedupeFilter( - filter.status - ? [...(filter.status as string[]), valueOrFirst(value)] - : arrayOrValue(value) - ) - }; - } -} + const { active, name, value } = filter; -interface OrderListChipFormatData { - formatDate: (date: string) => string; -} -export function createFilterChips( - filters: OrderListUrlFilters, - formatData: OrderListChipFormatData, - onFilterDelete: (filters: OrderListUrlFilters) => void, - intl: IntlShape -): Filter[] { - let filterChips: Filter[] = []; + if (active) { + switch (name) { + case OrderFilterKeys.created: + return { + createdFrom: value[0], + createdTo: value[1] + }; - if (!!filters.dateFrom || !!filters.dateTo) { - if (filters.dateFrom === filters.dateTo) { - filterChips = [ - ...filterChips, - { - label: intl.formatMessage(filterMessages.dateIs, { - date: formatData.formatDate(filters.dateFrom) - }), - onClick: () => - onFilterDelete({ - ...filters, - dateFrom: undefined, - dateTo: undefined - }) - } - ]; - } else { - if (!!filters.dateFrom) { - filterChips = [ - ...filterChips, - { - label: intl.formatMessage(filterMessages.dateFrom, { - date: formatData.formatDate(filters.dateFrom) - }), - onClick: () => - onFilterDelete({ - ...filters, - dateFrom: undefined - }) - } - ]; - } - - if (!!filters.dateTo) { - filterChips = [ - ...filterChips, - { - label: intl.formatMessage(filterMessages.dateTo, { - date: formatData.formatDate(filters.dateTo) - }), - onClick: () => - onFilterDelete({ - ...filters, - dateTo: undefined - }) - } - ]; - } + case OrderFilterKeys.status: + return { + status: value.map(val => findInEnum(val, OrderStatus)) + }; } } - - if (!!filters.email) { - filterChips = [ - ...filterChips, - { - label: filters.email, - onClick: () => - onFilterDelete({ - ...filters, - email: undefined - }) - } - ]; - } - - if (!!filters.status) { - const statusFilterChips = Array.isArray(filters.status) - ? filters.status.map((status, statusIndex) => ({ - label: getStatusLabel(status, intl), - onClick: () => - onFilterDelete({ - ...filters, - status: arrayOrUndefined( - removeAtIndex(filters.status as string[], statusIndex) - ) - }) - })) - : [ - { - label: getStatusLabel(filters.status, intl), - onClick: () => - onFilterDelete({ - ...filters, - status: undefined - }) - } - ]; - - filterChips = [...filterChips, ...statusFilterChips]; - } - - return filterChips; +} +export function createFilterQueryParams( + filter: IFilter +): OrderListUrlFilters { + return filter.reduce( + (acc, filterField) => ({ + ...acc, + ...getFilterQueryParam(filterField) + }), + {} + ); } export const { diff --git a/src/orders/views/OrderList/messages.ts b/src/orders/views/OrderList/messages.ts new file mode 100644 index 000000000..52dd258ae --- /dev/null +++ b/src/orders/views/OrderList/messages.ts @@ -0,0 +1,29 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + fulfilled: { + defaultMessage: "Fulfilled", + description: "order status" + }, + partiallyFulfilled: { + defaultMessage: "Partially Fulfilled", + description: "order status" + }, + placed: { + defaultMessage: "Placed", + description: "order" + }, + readyToCapture: { + defaultMessage: "Ready to Capture", + description: "order status" + }, + status: { + defaultMessage: "Order Status" + }, + unfulfilled: { + defaultMessage: "Unfulfilled", + description: "order status" + } +}); + +export default messages; diff --git a/src/products/components/ProductListPage/ProductListPage.tsx b/src/products/components/ProductListPage/ProductListPage.tsx index 4c58bfff0..eb50c0704 100644 --- a/src/products/components/ProductListPage/ProductListPage.tsx +++ b/src/products/components/ProductListPage/ProductListPage.tsx @@ -161,9 +161,6 @@ export const ProductListPage: React.FC = props => { description: "tab name" })} filterStructure={filterStructure} - filterLabel={intl.formatMessage({ - defaultMessage: "Select all products where:" - })} searchPlaceholder={intl.formatMessage({ defaultMessage: "Search Products..." })} diff --git a/src/storybook/stories/components/Filter.tsx b/src/storybook/stories/components/Filter.tsx index 2d4bf3b62..5875ebf68 100644 --- a/src/storybook/stories/components/Filter.tsx +++ b/src/storybook/stories/components/Filter.tsx @@ -13,7 +13,7 @@ import Decorator from "../../Decorator"; const props: FilterContentProps = { currencySymbol: "USD", filters: [ - createPriceField("price", "Price", "USD", { + createPriceField("price", "Price", { max: "100.00", min: "20.00" }), diff --git a/src/types.ts b/src/types.ts index cb4ef1d1b..d7204757c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -136,7 +136,7 @@ export type Filters = Partial< Record >; export type FiltersWithMultipleValues = Partial< - Record + Record >; export type SingleAction = Partial<{ id: string; From c155cf36e4b8b9a17cf95c1d1367f0ec0af32f4c Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 20 Dec 2019 11:53:28 +0100 Subject: [PATCH 06/85] Fix types --- src/components/Filter/useFilter.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/components/Filter/useFilter.ts b/src/components/Filter/useFilter.ts index dff73fdb6..0a9b5ccb3 100644 --- a/src/components/Filter/useFilter.ts +++ b/src/components/Filter/useFilter.ts @@ -3,12 +3,6 @@ import { useReducer, useEffect, Dispatch } from "react"; import reduceFilter, { FilterReducerAction } from "./reducer"; import { IFilter, IFilterElement } from "./types"; -function createInitialFilter( - initialFilter: IFilter -): IFilter { - return initialFilter; -} - export type UseFilter = [ Array>, Dispatch>, @@ -16,10 +10,9 @@ export type UseFilter = [ ]; function useFilter(initialFilter: IFilter): UseFilter { - const [data, dispatchFilterAction] = useReducer( - reduceFilter, - createInitialFilter(initialFilter) - ); + const [data, dispatchFilterAction] = useReducer< + React.Reducer, FilterReducerAction> + >(reduceFilter, initialFilter); const reset = () => dispatchFilterAction({ From 1f3cbfda8277d097a1faba6eac4f8fccae4939a2 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 20 Dec 2019 16:53:03 +0100 Subject: [PATCH 07/85] Code cleanup --- src/misc.ts | 8 +- .../OrderListPage/OrderListPage.tsx | 6 +- src/orders/types.ts | 7 ++ src/orders/views/OrderList/OrderList.tsx | 2 + src/orders/views/OrderList/filters.ts | 78 +++++++++++++------ .../ProductListPage/ProductListPage.tsx | 10 ++- src/products/types.ts | 13 ++++ .../views/ProductList/ProductList.tsx | 4 +- src/products/views/ProductList/filters.ts | 73 +++++++++++------ src/types.ts | 23 +++--- src/utils/filters/fields.ts | 5 +- 11 files changed, 156 insertions(+), 73 deletions(-) create mode 100644 src/orders/types.ts create mode 100644 src/products/types.ts diff --git a/src/misc.ts b/src/misc.ts index 0d5971e8d..5672298d9 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -351,14 +351,14 @@ export function findInEnum( export function findValueInEnum( needle: string, haystack: TEnum -) { +): TEnum[keyof TEnum] { const match = Object.entries(haystack).find(([_, value]) => value === needle); - if (!!match) { - return match[1] as TEnum; + if (!match) { + throw new Error(`Value ${needle} not found in enum`); } - throw new Error(`Value ${needle} not found in enum`); + return (needle as unknown) as TEnum[keyof TEnum]; } export function parseBoolean(a: string, defaultValue: boolean): boolean { diff --git a/src/orders/components/OrderListPage/OrderListPage.tsx b/src/orders/components/OrderListPage/OrderListPage.tsx index 151f2ad41..f4c23b968 100644 --- a/src/orders/components/OrderListPage/OrderListPage.tsx +++ b/src/orders/components/OrderListPage/OrderListPage.tsx @@ -21,11 +21,12 @@ import { import FilterBar from "@saleor/components/FilterBar"; import { OrderList_orders_edges_node } from "../../types/OrderList"; import OrderList from "../OrderList"; +import { OrderListFilterOpts } from "../../types"; export interface OrderListPageProps extends PageListProps, ListActions, - FilterPageProps, + FilterPageProps, SortPage { orders: OrderList_orders_edges_node[]; } @@ -34,6 +35,7 @@ const OrderListPage: React.FC = ({ currencySymbol, currentTab, initialSearch, + filterOpts, tabs, onAdd, onAll, @@ -46,7 +48,7 @@ const OrderListPage: React.FC = ({ }) => { const intl = useIntl(); - const filterStructure = createFilterStructure(intl); + const filterStructure = createFilterStructure(intl, filterOpts); return ( diff --git a/src/orders/types.ts b/src/orders/types.ts new file mode 100644 index 000000000..fd4de956e --- /dev/null +++ b/src/orders/types.ts @@ -0,0 +1,7 @@ +import { FilterOpts, MinMax } from "@saleor/types"; +import { OrderStatusFilter } from "@saleor/types/globalTypes"; + +export interface OrderListFilterOpts { + created: FilterOpts; + status: FilterOpts; +} diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 88ea50b18..aef862f0f 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -41,6 +41,7 @@ import { deleteFilterTab, getActiveFilters, getFilterTabs, + getFilterOpts, getFilterVariables, saveFilterTab, OrderFilterKeys, @@ -200,6 +201,7 @@ export const OrderList: React.FC = ({ params }) => { settings={settings} currentTab={currentTab} disabled={loading} + filterOpts={getFilterOpts(params)} orders={maybe(() => data.orders.edges.map(edge => edge.node))} pageInfo={pageInfo} sort={getSortParams(params)} diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index 614d9432e..d78f55f40 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -1,6 +1,11 @@ import { IntlShape } from "react-intl"; -import { findInEnum, maybe, orderStatusMessages } from "@saleor/misc"; +import { + findInEnum, + maybe, + orderStatusMessages, + findValueInEnum +} from "@saleor/misc"; import { createDateField, createOptionsField @@ -22,6 +27,7 @@ import { OrderListUrlFiltersWithMultipleValuesEnum, OrderListUrlQueryParams } from "../../urls"; +import { OrderListFilterOpts } from "../../types"; import messages from "./messages"; export const ORDER_FILTERS_KEY = "orderFilters"; @@ -31,39 +37,56 @@ export enum OrderFilterKeys { status = "status" } -export function createFilterStructure( - intl: IntlShape, +export function getFilterOpts( params: OrderListUrlFilters -): IFilter { - return [ - { - ...createDateField( - OrderFilterKeys.created, - intl.formatMessage(messages.placed), - { - max: maybe(() => params.createdTo, ""), - min: maybe(() => params.createdFrom, "") - } - ), +): OrderListFilterOpts { + return { + created: { active: maybe( () => [params.createdFrom, params.createdTo].some( field => field !== undefined ), false + ), + value: { + max: maybe(() => params.createdTo, ""), + min: maybe(() => params.createdFrom, "") + } + }, + status: { + active: maybe(() => params.status !== undefined, false), + value: maybe( + () => + dedupeFilter( + params.status.map(status => + findValueInEnum(status, OrderStatusFilter) + ) + ), + [] ) + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: OrderListFilterOpts +): IFilter { + return [ + { + ...createDateField( + OrderFilterKeys.created, + intl.formatMessage(messages.placed), + opts.created.value + ), + active: opts.created.active }, { ...createOptionsField( OrderFilterKeys.status, intl.formatMessage(messages.status), - maybe( - () => - dedupeFilter( - params.status.map(status => findInEnum(status, OrderStatusFilter)) - ), - [] - ), + opts.status.value, true, [ { @@ -84,7 +107,7 @@ export function createFilterStructure( } ] ), - active: maybe(() => params.status !== undefined, false) + active: opts.status.active } ]; } @@ -108,14 +131,21 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): OrderListUrlFilters { - const { active, name, value } = filter; + const { active, multiple, name, value } = filter; if (active) { switch (name) { case OrderFilterKeys.created: + if (multiple) { + return { + createdFrom: value[0], + createdTo: value[1] + }; + } + return { createdFrom: value[0], - createdTo: value[1] + createdTo: value[0] }; case OrderFilterKeys.status: diff --git a/src/products/components/ProductListPage/ProductListPage.tsx b/src/products/components/ProductListPage/ProductListPage.tsx index eb50c0704..074952648 100644 --- a/src/products/components/ProductListPage/ProductListPage.tsx +++ b/src/products/components/ProductListPage/ProductListPage.tsx @@ -24,17 +24,18 @@ import { SortPage } from "@saleor/types"; import FilterBar from "@saleor/components/FilterBar"; +import { ProductListFilterOpts } from "@saleor/products/types"; +import { ProductListUrlSortField } from "../../urls"; import { createFilterStructure, ProductFilterKeys -} from "@saleor/products/views/ProductList/filters"; -import { ProductListUrlSortField } from "../../urls"; +} from "../../views/ProductList/filters"; import ProductList from "../ProductList"; export interface ProductListPageProps extends PageListProps, ListActions, - FilterPageProps, + FilterPageProps, FetchMoreProps, SortPage { activeAttributeSortId: string; @@ -61,6 +62,7 @@ export const ProductListPage: React.FC = props => { defaultSettings, gridAttributes, availableInGridAttributes, + filterOpts, hasMore, initialSearch, loading, @@ -84,7 +86,7 @@ export const ProductListPage: React.FC = props => { const handleSave = (columns: ProductListColumns[]) => onUpdateListSettings("columns", columns); - const filterStructure = createFilterStructure(intl); + const filterStructure = createFilterStructure(intl, filterOpts); const columns: ColumnPickerChoice[] = [ { diff --git a/src/products/types.ts b/src/products/types.ts new file mode 100644 index 000000000..f6dfd9396 --- /dev/null +++ b/src/products/types.ts @@ -0,0 +1,13 @@ +import { FilterOpts, MinMax } from "@saleor/types"; +import { StockAvailability } from "@saleor/types/globalTypes"; + +export enum ProductStatus { + PUBLISHED = "published", + HIDDEN = "hidden" +} + +export interface ProductListFilterOpts { + price: FilterOpts; + status: FilterOpts; + stockStatus: FilterOpts; +} diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 1a0aafb88..45b2586c2 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -54,7 +54,8 @@ import { getFilterVariables, saveFilterTab, ProductFilterKeys, - createFilterQueryParams + createFilterQueryParams, + getFilterOpts } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -234,6 +235,7 @@ export const ProductList: React.FC = ({ params }) => { defaultSettings={ defaultListSettings[ListViews.PRODUCT_LIST] } + filterOpts={getFilterOpts(params)} gridAttributes={maybe( () => attributes.data.grid.edges.map(edge => edge.node), diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 21cc8ff50..5d2d0b21e 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -1,10 +1,11 @@ import { IntlShape } from "react-intl"; -import { findInEnum, maybe } from "@saleor/misc"; +import { maybe, findValueInEnum } from "@saleor/misc"; import { createOptionsField, createPriceField } from "@saleor/utils/filters/fields"; +import { ProductStatus, ProductListFilterOpts } from "@saleor/products/types"; import { IFilterElement, IFilter } from "../../../components/Filter"; import { ProductFilterInput, @@ -29,21 +30,42 @@ export enum ProductFilterKeys { stock = "stock" } -export enum ProductStatus { - PUBLISHED = "published", - HIDDEN = "hidden" +export function getFilterOpts( + params: ProductListUrlFilters +): ProductListFilterOpts { + return { + price: { + active: maybe( + () => + [params.priceFrom, params.priceTo].some(field => field !== undefined), + false + ), + value: { + max: maybe(() => params.priceTo, "0"), + min: maybe(() => params.priceFrom, "0") + } + }, + status: { + active: maybe(() => params.status !== undefined, false), + value: maybe(() => findValueInEnum(params.status, ProductStatus)) + }, + stockStatus: { + active: maybe(() => params.stockStatus !== undefined, false), + value: maybe(() => findValueInEnum(params.stockStatus, StockAvailability)) + } + }; } export function createFilterStructure( intl: IntlShape, - params: ProductListUrlFilters + opts: ProductListFilterOpts ): IFilter { return [ { ...createOptionsField( ProductFilterKeys.status, intl.formatMessage(messages.visibility), - [ProductStatus.PUBLISHED], + [opts.status.value], false, [ { @@ -56,13 +78,13 @@ export function createFilterStructure( } ] ), - active: maybe(() => params.status !== undefined, false) + active: opts.status.active }, { ...createOptionsField( ProductFilterKeys.stock, intl.formatMessage(messages.quantity), - [StockAvailability.IN_STOCK], + [opts.stockStatus.value], false, [ { @@ -75,22 +97,15 @@ export function createFilterStructure( } ] ), - active: maybe(() => params.stockStatus !== undefined, false) + active: opts.stockStatus.active }, { ...createPriceField( ProductFilterKeys.price, intl.formatMessage(messages.price), - { - max: maybe(() => params.priceTo, "0"), - min: maybe(() => params.priceFrom, "0") - } + opts.price.value ), - active: maybe( - () => - [params.priceFrom, params.priceTo].some(field => field !== undefined), - false - ) + active: opts.price.active } ]; } @@ -108,33 +123,41 @@ export function getFilterVariables( lte: parseFloat(params.priceTo) }, search: params.query, - stockAvailability: StockAvailability[params.stockStatus] + stockAvailability: + params.stockStatus !== undefined + ? findValueInEnum(params.stockStatus, StockAvailability) + : null }; } export function getFilterQueryParam( filter: IFilterElement ): ProductListUrlFilters { - const { active, name, value } = filter; + const { active, multiple, name, value } = filter; if (active) { switch (name) { case ProductFilterKeys.price: + if (multiple) { + return { + priceFrom: value[0], + priceTo: value[1] + }; + } + return { priceFrom: value[0], - priceTo: value[1] + priceTo: value[0] }; case ProductFilterKeys.status: return { - status: ( - findInEnum(value[0], ProductStatus) === ProductStatus.PUBLISHED - ).toString() + status: findValueInEnum(value[0], ProductStatus) }; case ProductFilterKeys.stock: return { - status: findInEnum(value[0], StockAvailability) + stockStatus: findValueInEnum(value[0], StockAvailability) }; } } diff --git a/src/types.ts b/src/types.ts index d7204757c..977cd1bf2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -82,20 +82,16 @@ export interface SearchPageProps { initialSearch: string; onSearchChange: (value: string) => void; } -export interface FilterPageProps - extends SearchPageProps, +export interface FilterPageProps + extends FilterProps, + SearchPageProps, TabPageProps { - currencySymbol: string; - onFilterChange: (filter: IFilter) => void; + filterOpts: TOpts; } -export interface SearchProps { - searchPlaceholder: string; -} -export interface FilterProps - extends FilterPageProps, - SearchProps { +export interface FilterProps { currencySymbol: string; + onFilterChange: (filter: IFilter) => void; } export interface TabPageProps { @@ -170,3 +166,10 @@ export interface UserPermissionProps { export interface MutationResultAdditionalProps { status: ConfirmButtonTransitionState; } + +export type MinMax = Record<"min" | "max", string>; + +export interface FilterOpts { + active: boolean; + value: T; +} diff --git a/src/utils/filters/fields.ts b/src/utils/filters/fields.ts index a56dfeb8b..3d5d4cd91 100644 --- a/src/utils/filters/fields.ts +++ b/src/utils/filters/fields.ts @@ -1,7 +1,6 @@ import { IFilterElement, FieldType } from "@saleor/components/Filter"; import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; - -type MinMax = Record<"min" | "max", string>; +import { MinMax } from "@saleor/types"; export function createPriceField( name: T, @@ -26,7 +25,7 @@ export function createDateField( return { active: false, label, - multiple: true, + multiple: defaultValue.min !== defaultValue.max, name, type: FieldType.date, value: [defaultValue.min, defaultValue.max] From dab5dca61530c0742161bee3304766c4ff7bbf1b Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 20 Dec 2019 17:03:20 +0100 Subject: [PATCH 08/85] Visual improvements --- src/components/Filter/Filter.tsx | 9 +----- src/components/FilterBar/FilterBar.tsx | 43 ++++++++++++++------------ src/components/SearchBar/SearchBar.tsx | 43 ++++++++++++++------------ 3 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index dbe8cd1e4..fd49209f1 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -4,7 +4,6 @@ import Popper from "@material-ui/core/Popper"; import { makeStyles } from "@material-ui/core/styles"; import { fade } from "@material-ui/core/styles/colorManipulator"; import Typography from "@material-ui/core/Typography"; -import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown"; import classNames from "classnames"; import React from "react"; import { FormattedMessage } from "react-intl"; @@ -97,14 +96,8 @@ const Filter: React.FC = props => { onClick={() => setFilterMenuOpened(!isFilterMenuOpened)} > - + - extends FilterProps, @@ -20,14 +19,15 @@ export interface FilterBarProps const useStyles = makeStyles( theme => ({ root: { + borderBottom: `1px solid ${theme.palette.divider}`, display: "flex", flexWrap: "wrap", padding: theme.spacing(1, 3) }, - tabActions: { - borderBottom: `1px solid ${theme.palette.divider}`, - padding: theme.spacing(1, 3, 2), - textAlign: "right" + tabActionButton: { + marginLeft: theme.spacing(2), + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3) } }), { @@ -93,30 +93,33 @@ const FilterBar: React.FC = props => { placeholder={searchPlaceholder} onSearchChange={onSearchChange} /> -
- {displayTabAction === null ? ( -
- ) : ( -
- {displayTabAction === "save" ? ( - + {displayTabAction && + (displayTabAction === "save" ? ( + ) : ( displayTabAction === "delete" && ( - + ) - )} -
- )} + ))} + ); }; diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 9636c5ac0..17c66b4bb 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -1,11 +1,10 @@ import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import makeStyles from "@material-ui/core/styles/makeStyles"; +import Button from "@material-ui/core/Button"; import { SearchPageProps, TabPageProps } from "@saleor/types"; import FilterTabs, { FilterTab } from "../TableFilter"; -import Link from "../Link"; -import Hr from "../Hr"; import SearchInput from "./SearchInput"; export interface SearchBarProps extends SearchPageProps, TabPageProps { @@ -16,14 +15,15 @@ export interface SearchBarProps extends SearchPageProps, TabPageProps { const useStyles = makeStyles( theme => ({ root: { + borderBottom: `1px solid ${theme.palette.divider}`, display: "flex", flexWrap: "wrap", padding: theme.spacing(1, 3) }, - tabActions: { - borderBottom: `1px solid ${theme.palette.divider}`, - padding: theme.spacing(1, 3, 2), - textAlign: "right" + tabActionButton: { + marginLeft: theme.spacing(2), + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3) } }), { @@ -81,30 +81,33 @@ const SearchBar: React.FC = props => { placeholder={searchPlaceholder} onSearchChange={onSearchChange} /> - - {displayTabAction === null ? ( -
- ) : ( -
- {displayTabAction === "save" ? ( - + {displayTabAction && + (displayTabAction === "save" ? ( + ) : ( displayTabAction === "delete" && ( - + ) - )} -
- )} + ))} + ); }; From 238bff1d9b0076956d55279f0a6d56bbc8a4cb29 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 20 Dec 2019 17:08:25 +0100 Subject: [PATCH 09/85] Improve copy --- src/components/Filter/FilterContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index 8f3c24b21..4e3f72886 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -78,14 +78,14 @@ function getIsFilterMultipleChoices( return [ { label: intl.formatMessage({ - defaultMessage: "is equal to", + defaultMessage: "equal to", description: "is filter range or value" }), value: FilterType.SINGULAR }, { label: intl.formatMessage({ - defaultMessage: "is between", + defaultMessage: "between", description: "is filter range or value" }), value: FilterType.MULTIPLE From fe6996c0cfb51005d7c0af7e1c97db730928fe08 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 20 Dec 2019 17:31:26 +0100 Subject: [PATCH 10/85] Add active filters indicator --- src/components/Filter/Filter.tsx | 23 +++++++++++++++++-- .../OrderListPage/OrderListPage.tsx | 4 ++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index fd49209f1..148f1175d 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -46,7 +46,6 @@ const useStyles = makeStyles( color: theme.palette.primary.main, fontSize: 14, fontWeight: 600 as 600, - marginRight: 4, textTransform: "uppercase" }, filterButton: { @@ -75,6 +74,13 @@ const useStyles = makeStyles( }, rotate: { transform: "rotate(180deg)" + }, + separator: { + backgroundColor: theme.palette.primary.main, + display: "inline-block", + height: 28, + margin: theme.spacing(0, 1.5, 0, 1), + width: 1 } }), { name: "Filter" } @@ -87,17 +93,30 @@ const Filter: React.FC = props => { const [isFilterMenuOpened, setFilterMenuOpened] = React.useState(false); const [data, dispatch, reset] = useFilter(menu); + const isFilterActive = menu.some(filterElement => filterElement.active); + return (
setFilterMenuOpened(!isFilterMenuOpened)} > + {isFilterActive && ( + <> + + + {menu.reduce( + (acc, filterElement) => acc + (filterElement.active ? 1 : 0), + 0 + )} + + + )} = ({ onTabSave={onTabSave} tabs={tabs} allTabLabel={intl.formatMessage({ - defaultMessage: "All Products", + defaultMessage: "All Orders", description: "tab name" })} filterStructure={filterStructure} searchPlaceholder={intl.formatMessage({ - defaultMessage: "Search Products..." + defaultMessage: "Search Orders..." })} /> From 9e0080095630324d094b7dd3974bb1cd6d418490 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 20 Dec 2019 17:40:19 +0100 Subject: [PATCH 11/85] Fix filter toggling --- src/orders/views/OrderList/filters.ts | 37 ++++++++++------- src/products/views/ProductList/filters.ts | 50 +++++++++++++++-------- 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index d78f55f40..97482955c 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -133,26 +133,35 @@ export function getFilterQueryParam( ): OrderListUrlFilters { const { active, multiple, name, value } = filter; - if (active) { - switch (name) { - case OrderFilterKeys.created: - if (multiple) { - return { - createdFrom: value[0], - createdTo: value[1] - }; - } - + switch (name) { + case OrderFilterKeys.created: + if (!active) { + return { + createdFrom: undefined, + createdTo: undefined + }; + } + if (multiple) { return { createdFrom: value[0], - createdTo: value[0] + createdTo: value[1] }; + } - case OrderFilterKeys.status: + return { + createdFrom: value[0], + createdTo: value[0] + }; + + case OrderFilterKeys.status: + if (!active) { return { - status: value.map(val => findInEnum(val, OrderStatus)) + status: undefined }; - } + } + return { + status: value.map(val => findInEnum(val, OrderStatus)) + }; } } export function createFilterQueryParams( diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 5d2d0b21e..4f9c486a5 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -135,31 +135,45 @@ export function getFilterQueryParam( ): ProductListUrlFilters { const { active, multiple, name, value } = filter; - if (active) { - switch (name) { - case ProductFilterKeys.price: - if (multiple) { - return { - priceFrom: value[0], - priceTo: value[1] - }; - } - + switch (name) { + case ProductFilterKeys.price: + if (!active) { + return { + priceFrom: undefined, + priceTo: undefined + }; + } + if (multiple) { return { priceFrom: value[0], - priceTo: value[0] + priceTo: value[1] }; + } - case ProductFilterKeys.status: - return { - status: findValueInEnum(value[0], ProductStatus) - }; + return { + priceFrom: value[0], + priceTo: value[0] + }; - case ProductFilterKeys.stock: + case ProductFilterKeys.status: + if (!active) { return { - stockStatus: findValueInEnum(value[0], StockAvailability) + status: undefined }; - } + } + return { + status: findValueInEnum(value[0], ProductStatus) + }; + + case ProductFilterKeys.stock: + if (!active) { + return { + stockStatus: undefined + }; + } + return { + stockStatus: findValueInEnum(value[0], StockAvailability) + }; } } export function createFilterQueryParams( From e520f30a86053285451d2b5a66b0d166b8135fb4 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 20 Dec 2019 17:52:29 +0100 Subject: [PATCH 12/85] Fix stories --- src/fixtures.ts | 73 +------------------ .../stories/orders/OrderListPage.tsx | 18 ++++- .../stories/products/ProductListPage.tsx | 24 ++++-- 3 files changed, 36 insertions(+), 79 deletions(-) diff --git a/src/fixtures.ts b/src/fixtures.ts index aff42ef0d..5b45c2f75 100644 --- a/src/fixtures.ts +++ b/src/fixtures.ts @@ -1,5 +1,4 @@ import { ShopInfo_shop_permissions } from "./components/Shop/types/ShopInfo"; -import { Filter } from "./components/TableFilter"; import { FetchMoreProps, FilterPageProps, @@ -303,80 +302,14 @@ export const searchPageProps: SearchPageProps = { onSearchChange: () => undefined }; -export const filterPageProps: FilterPageProps = { +export const filterPageProps: FilterPageProps = { ...searchPageProps, ...tabPageProps, currencySymbol: "USD", - filtersList: [], - onFilterAdd: () => undefined + filterOpts: {}, + onFilterChange: () => undefined }; -export const filters: Filter[] = [ - { - label: "Property X is ", - onClick: () => undefined - }, - { - label: "Property Y is ", - onClick: () => undefined - }, - { - label: "Property Z is ", - onClick: () => undefined - }, - { - label: "Property X is ", - onClick: () => undefined - }, - { - label: "Property Y is ", - onClick: () => undefined - }, - { - label: "Property Z is ", - onClick: () => undefined - }, - { - label: "Property X is ", - onClick: () => undefined - }, - { - label: "Property Y is ", - onClick: () => undefined - }, - { - label: "Property Z is ", - onClick: () => undefined - }, - { - label: "Property X is ", - onClick: () => undefined - }, - { - label: "Property Y is ", - onClick: () => undefined - }, - { - label: "Property Z is ", - onClick: () => undefined - }, - { - label: "Property X is ", - onClick: () => undefined - }, - { - label: "Property Y is ", - onClick: () => undefined - }, - { - label: "Property Z is ", - onClick: () => undefined - } -].map((filter, filterIndex) => ({ - ...filter, - label: filter.label + filterIndex -})); - export const fetchMoreProps: FetchMoreProps = { hasMore: true, loading: false, diff --git a/src/storybook/stories/orders/OrderListPage.tsx b/src/storybook/stories/orders/OrderListPage.tsx index c3095aee4..5fb565871 100644 --- a/src/storybook/stories/orders/OrderListPage.tsx +++ b/src/storybook/stories/orders/OrderListPage.tsx @@ -5,9 +5,9 @@ import OrderListPage, { OrderListPageProps } from "@saleor/orders/components/OrderListPage"; import { OrderListUrlSortField } from "@saleor/orders/urls"; +import { OrderStatusFilter } from "@saleor/types/globalTypes"; import { filterPageProps, - filters, listActionsProps, pageListProps, sortPageProps @@ -20,6 +20,19 @@ const props: OrderListPageProps = { ...pageListProps.default, ...filterPageProps, ...sortPageProps, + filterOpts: { + created: { + active: false, + value: { + max: "400", + min: "50" + } + }, + status: { + active: false, + value: [OrderStatusFilter.CANCELED, OrderStatusFilter.FULFILLED] + } + }, orders, sort: { ...sortPageProps.sort, @@ -30,9 +43,6 @@ const props: OrderListPageProps = { storiesOf("Views / Orders / Order list", module) .addDecorator(Decorator) .add("default", () => ) - .add("with custom filters", () => ( - - )) .add("loading", () => ( ) - .add("with custom filters", () => ( - - )) .add("loading", () => ( From bfd754b9b2b801ccb55d34a0d7e1bdaf3efc82b0 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 30 Dec 2019 13:51:01 +0100 Subject: [PATCH 13/85] Simplify code --- src/orders/views/OrderList/OrderList.tsx | 31 ++++++++----------- .../views/ProductList/ProductList.tsx | 26 ++++++++-------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index aef862f0f..8b32ee357 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -31,7 +31,6 @@ import { OrderBulkCancel } from "../../types/OrderBulkCancel"; import { OrderDraftCreate } from "../../types/OrderDraftCreate"; import { orderListUrl, - OrderListUrlFilters, OrderListUrlQueryParams, orderUrl, OrderListUrlDialog @@ -88,17 +87,7 @@ export const OrderList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filters: OrderListUrlFilters) => { - reset(); - navigate( - orderListUrl({ - ...params, - ...filters - }) - ); - }; - - const changeFilterField = (filter: IFilter) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( orderListUrl({ @@ -109,6 +98,16 @@ export const OrderList: React.FC = ({ params }) => { ); }; + const resetFilters = () => { + reset(); + navigate( + orderListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + const handleSearchChange = (query: string) => { reset(); navigate( @@ -231,17 +230,13 @@ export const OrderList: React.FC = ({ params }) => { } onSearchChange={handleSearchChange} - onFilterChange={filter => changeFilterField(filter)} + onFilterChange={filter => changeFilters(filter)} onTabSave={() => openModal("save-search")} onTabDelete={() => openModal("delete-search")} onTabChange={handleTabChange} initialSearch={params.query || ""} tabs={getFilterTabs().map(tab => tab.name)} - onAll={() => - changeFilters({ - status: undefined - }) - } + onAll={resetFilters} /> = ({ params }) => { ProductListUrlQueryParams >(navigate, productListUrl, params); - const changeFilters = (filters: ProductListUrlFilters) => { - reset(); - navigate(productListUrl(filters)); - }; - - const changeFilterField = (filter: IFilter) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( productListUrl({ @@ -119,6 +113,16 @@ export const ProductList: React.FC = ({ params }) => { ); }; + const resetFilters = () => { + reset(); + navigate( + productListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + const handleSearchChange = (query: string) => { reset(); navigate( @@ -291,11 +295,7 @@ export const ProductList: React.FC = ({ params }) => { onUpdateListSettings={updateListSettings} pageInfo={pageInfo} onRowClick={id => () => navigate(productUrl(id))} - onAll={() => - changeFilters({ - status: undefined - }) - } + onAll={resetFilters} toolbar={ <> + +
- +
+
+ +
+
+
+
+
+
+
+ + + +
+
+
+ + +
+
+ + and + +
+
+ + +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+ + +`; + +exports[`Storyshots Generics / Filter interactive 1`] = ` +
+
+
+
+
+
+ Filters +
+
+ + +
+
+
+
+ +
+
+
+
+
+
+
+ + + +
+
+
+ + +
+
+ + and + +
+
+ + +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
@@ -11587,17 +12211,17 @@ exports[`Storyshots Views / Attributes / Attribute list default 1`] = `
-
@@ -12566,17 +13187,17 @@ exports[`Storyshots Views / Attributes / Attribute list loading 1`] = `
-
@@ -12961,17 +13579,17 @@ exports[`Storyshots Views / Attributes / Attribute list no data 1`] = `
-
@@ -14204,17 +14819,17 @@ exports[`Storyshots Views / Categories / Category list default 1`] = `
-
@@ -14753,17 +15365,17 @@ exports[`Storyshots Views / Categories / Category list empty 1`] = `
-
@@ -15074,17 +15683,17 @@ exports[`Storyshots Views / Categories / Category list loading 1`] = `
-
@@ -26351,17 +26957,17 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
-
@@ -26937,17 +27540,17 @@ exports[`Storyshots Views / Collections / Collection list loading 1`] = `
-
@@ -27318,17 +27918,17 @@ exports[`Storyshots Views / Collections / Collection list no data 1`] = `
-
@@ -37327,17 +37924,17 @@ exports[`Storyshots Views / Customers / Customer list default 1`] = `
-
@@ -38470,17 +39064,17 @@ exports[`Storyshots Views / Customers / Customer list loading 1`] = `
-
@@ -38842,17 +39433,17 @@ exports[`Storyshots Views / Customers / Customer list no data 1`] = `
-
@@ -44350,17 +44938,17 @@ exports[`Storyshots Views / Discounts / Sale list default 1`] = `
-
@@ -44916,17 +45501,17 @@ exports[`Storyshots Views / Discounts / Sale list loading 1`] = `
-
@@ -45312,17 +45894,17 @@ exports[`Storyshots Views / Discounts / Sale list no data 1`] = `
-
@@ -50532,17 +51111,17 @@ exports[`Storyshots Views / Discounts / Voucher list default 1`] = `
-
@@ -51011,17 +51587,17 @@ exports[`Storyshots Views / Discounts / Voucher list loading 1`] = `
-
@@ -51453,17 +52026,17 @@ exports[`Storyshots Views / Discounts / Voucher list no data 1`] = `
-
@@ -55949,17 +56519,17 @@ exports[`Storyshots Views / Orders / Draft order list default 1`] = `
-
@@ -57191,17 +57758,17 @@ exports[`Storyshots Views / Orders / Draft order list loading 1`] = `
-
@@ -57592,17 +58156,17 @@ exports[`Storyshots Views / Orders / Draft order list when no data 1`] = `
-
@@ -73101,7 +73662,7 @@ exports[`Storyshots Views / Orders / Order list default 1`] = `
-
-
@@ -74754,7 +75301,7 @@ exports[`Storyshots Views / Orders / Order list loading 1`] = `
-
+ -
@@ -75225,7 +75769,7 @@ exports[`Storyshots Views / Orders / Order list when no data 1`] = `
-
-
@@ -75535,2054 +76065,6 @@ exports[`Storyshots Views / Orders / Order list when no data 1`] = `
`; -exports[`Storyshots Views / Orders / Order list with custom filters 1`] = ` -
-
-
-
- Orders -
-
-
- -
-
-
-
-
-
-
- - -
-
-
-
-
- -
-
-
- - -
-
-
-
-
-
-
- Property X is 0 -
- -
-
-
- Property Y is 1 -
- -
-
-
- Property Z is 2 -
- -
-
-
- Property X is 3 -
- -
-
-
- Property Y is 4 -
- -
-
-
- Property Z is 5 -
- -
-
-
- Property X is 6 -
- -
-
-
- Property Y is 7 -
- -
-
-
- Property Z is 8 -
- -
-
-
- Property X is 9 -
- -
-
-
- Property Y is 10 -
- -
-
-
- Property Z is 11 -
- -
-
-
- Property X is 12 -
- -
-
-
- Property Y is 13 -
- -
-
-
- Property Z is 14 -
- -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- No. of Order -
- -
-
-
-
- Date -
-
-
-
-
- Customer -
-
-
-
-
- Payment -
-
-
-
-
- Fulfillment status -
-
-
-
-
- Total -
-
-
-
-
-
-
-`; - exports[`Storyshots Views / Pages / Page details default 1`] = `
-
@@ -86450,17 +84929,17 @@ exports[`Storyshots Views / Product types / Product types list loading 1`] = `
-
@@ -86785,17 +85261,17 @@ exports[`Storyshots Views / Product types / Product types list no data 1`] = `
-
@@ -87340,7 +85813,7 @@ exports[`Storyshots Views / Products / Create multiple variants / summary defaul > ​ @@ -87458,7 +85931,7 @@ exports[`Storyshots Views / Products / Create multiple variants / summary defaul > ​ @@ -98330,6 +96803,99 @@ Ctrl + K"
+
+
+
+
+
+
+ Id sit dolores adipisci +
+
+
+
+
+ Id sit dolores adipisci +
+
+
+
+
+ Id sit dolores adipisci +
+
+
+
+
+ Id sit dolores adipisci +
+
+
+
+
+ Id sit dolores adipisci +
+
+
-
-
-
-
-
-
- Id sit dolores adipisci -
-
-
-
-
- Id sit dolores adipisci -
-
-
-
-
- Id sit dolores adipisci -
-
-
-
-
- Id sit dolores adipisci -
-
-
-
-
- Id sit dolores adipisci -
-
-
​ @@ -100137,7 +98610,7 @@ Ctrl + K" > ​ @@ -104524,7 +102997,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = `
-
-
@@ -105522,7 +103981,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = ` class="MuiTableCell-root-id MuiTableCell-body-id ProductList-colType-id" data-tc="product-type" > - Juice + Top (clothing) - Top (clothing) + Juice - Polo Shirt + Black Hoodie
@@ -105904,7 +104363,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = `
- Black Hoodie + Blue Hoodie
@@ -105969,7 +104428,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = `
- Blue Hoodie + Mustard Hoodie
@@ -106034,7 +104493,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = `
- Mustard Hoodie + Polo Shirt
@@ -106042,7 +104501,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = ` class="MuiTableCell-root-id MuiTableCell-body-id ProductList-colType-id" data-tc="product-type" > - Top (clothing) + Cushion - Cushion + Top (clothing)
-
+ -
@@ -106741,7 +105197,7 @@ exports[`Storyshots Views / Products / Product list no data 1`] = `
-
-
@@ -107046,2116 +105488,6 @@ exports[`Storyshots Views / Products / Product list no data 1`] = `
`; -exports[`Storyshots Views / Products / Product list with custom filters 1`] = ` -
-
-
-
- Products -
-
-
-
- -
- -
-
-
-
-
-
-
- - -
-
-
-
-
- -
-
-
- - -
-
-
-
-
-
-
- Property X is 0 -
- -
-
-
- Property Y is 1 -
- -
-
-
- Property Z is 2 -
- -
-
-
- Property X is 3 -
- -
-
-
- Property Y is 4 -
- -
-
-
- Property Z is 5 -
- -
-
-
- Property X is 6 -
- -
-
-
- Property Y is 7 -
- -
-
-
- Property Z is 8 -
- -
-
-
- Property X is 9 -
- -
-
-
- Property Y is 10 -
- -
-
-
- Property Z is 11 -
- -
-
-
- Property X is 12 -
- -
-
-
- Property Y is 13 -
- -
-
-
- Property Z is 14 -
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - Name - -
- -
-
-
-
- Type -
-
-
-
-
- Published -
-
-
-
-
- Price -
-
-
-
-
-
-
-
-`; - exports[`Storyshots Views / Products / Product variant details attribute errors 1`] = `
-
@@ -115596,17 +111925,17 @@ exports[`Storyshots Views / Services / Service list loading 1`] = `
-
@@ -115951,17 +112277,17 @@ exports[`Storyshots Views / Services / Service list no data 1`] = `
-
@@ -124770,17 +121093,17 @@ exports[`Storyshots Views / Staff / Staff members default 1`] = `
-
@@ -125439,17 +121759,17 @@ exports[`Storyshots Views / Staff / Staff members when loading 1`] = `
-
@@ -127068,17 +123385,17 @@ exports[`Storyshots Views / Translations / Entity list default 1`] = `
-
@@ -130952,17 +127266,17 @@ exports[`Storyshots Views / Webhooks / Webhook list default 1`] = `
-
@@ -131375,17 +127686,17 @@ exports[`Storyshots Views / Webhooks / Webhook list loading 1`] = `
-
@@ -131747,17 +128055,17 @@ exports[`Storyshots Views / Webhooks / Webhook list no data 1`] = `
-
From 8bdf4deabba622c5cc02118b8dde1858136494a7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 30 Dec 2019 15:08:21 +0100 Subject: [PATCH 17/85] Update messages --- locale/messages.pot | 488 +++++++++++--------------------------------- 1 file changed, 124 insertions(+), 364 deletions(-) diff --git a/locale/messages.pot b/locale/messages.pot index 2d58e0b2e..27e30eb13 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-12-04T14:17:34.264Z\n" +"POT-Creation-Date: 2019-12-30T13:44:02.183Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -167,14 +167,6 @@ msgctxt "page header" msgid "Add Collection" msgstr "" -#: build/locale/src/components/Filter/Filter.json -#. [src.components.Filter.2852521946] - button -#. defaultMessage is: -#. Add Filter -msgctxt "button" -msgid "Add Filter" -msgstr "" - #: build/locale/src/components/RichTextEditor/ImageSource.json #. [src.components.RichTextEditor.1603794322] - dialog header #. defaultMessage is: @@ -255,14 +247,6 @@ msgctxt "button" msgid "Add authentication" msgstr "" -#: build/locale/src/components/Filter/FilterContent.json -#. [src.components.Filter.2851720415] - button -#. defaultMessage is: -#. Add filter -msgctxt "button" -msgid "Add filter" -msgstr "" - #: build/locale/src/siteSettings/components/SiteSettingsKeys/SiteSettingsKeys.json #. [src.siteSettings.components.SiteSettingsKeys.1114030884] - button #. defaultMessage is: @@ -559,8 +543,8 @@ msgctxt "tab name" msgid "All Product Types" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.821159718] - tab name +#: build/locale/src/products/components/ProductListPage/ProductListPage.json +#. [src.products.components.ProductListPage.821159718] - tab name #. defaultMessage is: #. All Products msgctxt "tab name" @@ -867,7 +851,7 @@ msgctxt "description" msgid "Are you sure you want to delete {counter,plural,one{this customer} other{{displayQuantity} customers}}?" msgstr "" -#: build/locale/src/navigation/views/MenuList.json +#: build/locale/src/navigation/views/MenuList/MenuList.json #. [menuListDeleteMenusContent] #. defaultMessage is: #. Are you sure you want to delete {counter,plural,one{this menu} other{{displayQuantity} menus}}? @@ -883,8 +867,8 @@ msgctxt "dialog content" msgid "Are you sure you want to delete {counter,plural,one{this order draft} other{{displayQuantity} orderDrafts}}?" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.808633099] - dialog content +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.808633099] - dialog content #. defaultMessage is: #. Are you sure you want to delete {counter,plural,one{this page} other{{displayQuantity} pages}}? msgctxt "dialog content" @@ -971,7 +955,7 @@ msgctxt "delete customer, dialog content" msgid "Are you sure you want to delete {email}?" msgstr "" -#: build/locale/src/navigation/views/MenuList.json +#: build/locale/src/navigation/views/MenuList/MenuList.json #. [menuListDeleteMenuContent] #. defaultMessage is: #. Are you sure you want to delete {menuName}? @@ -1099,8 +1083,8 @@ msgctxt "description" msgid "Are you sure you want to publish {counter,plural,one{this collection} other{{displayQuantity} collections}}?" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.64432778] - dialog content +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.64432778] - dialog content #. defaultMessage is: #. Are you sure you want to publish {counter,plural,one{this page} other{{displayQuantity} pages}}? msgctxt "dialog content" @@ -1191,8 +1175,8 @@ msgctxt "description" msgid "Are you sure you want to unpublish {counter,plural,one{this collection} other{{displayQuantity} collections}}?" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.1265636351] - dialog content +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.1265636351] - dialog content #. defaultMessage is: #. Are you sure you want to unpublish {counter,plural,one{this page} other{{displayQuantity} pages}}? msgctxt "dialog content" @@ -1527,14 +1511,6 @@ msgctxt "description" msgid "Availability" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.2157131639] - product status -#. defaultMessage is: -#. Available -msgctxt "product status" -msgid "Available" -msgstr "" - #: build/locale/src/products/components/ProductVariants/ProductVariants.json #. [src.products.components.ProductVariants.2157131639] - product variant status #. defaultMessage is: @@ -1543,11 +1519,11 @@ msgctxt "product variant status" msgid "Available" msgstr "" -#: build/locale/src/products/views/ProductList/filters.json -#. [src.products.views.ProductList.available] - filter products by stock +#: build/locale/src/products/views/ProductList/messages.json +#. [src.products.views.ProductList.available] - product status #. defaultMessage is: #. Available -msgctxt "filter products by stock" +msgctxt "product status" msgid "Available" msgstr "" @@ -1819,10 +1795,6 @@ msgstr "" #. [src.categories.components.CategoryList.1214235329] #. defaultMessage is: #. Category Name -#: build/locale/src/collections/components/CollectionList/CollectionList.json -#. [src.collections.components.CollectionList.1214235329] -#. defaultMessage is: -#. Category Name #: build/locale/src/translations/components/TranslationsCategoriesPage/TranslationsCategoriesPage.json #. [src.translations.components.TranslationsCategoriesPage.1214235329] #. defaultMessage is: @@ -1975,6 +1947,10 @@ msgctxt "description" msgid "Collection" msgstr "" +#: build/locale/src/collections/components/CollectionList/CollectionList.json +#. [src.collections.components.CollectionList.2759199473] +#. defaultMessage is: +#. Collection Name #: build/locale/src/translations/components/TranslationsCollectionsPage/TranslationsCollectionsPage.json #. [src.translations.components.TranslationsCollectionsPage.2759199473] #. defaultMessage is: @@ -2751,7 +2727,7 @@ msgctxt "description" msgid "Created collection" msgstr "" -#: build/locale/src/navigation/views/MenuList.json +#: build/locale/src/navigation/views/MenuList/MenuList.json #. [menuListCreatedMenu] #. defaultMessage is: #. Created menu @@ -2927,38 +2903,6 @@ msgctxt "date when order was placed" msgid "Date" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.4205493358] -#. defaultMessage is: -#. Date -msgctxt "description" -msgid "Date" -msgstr "" - -#: build/locale/src/orders/views/OrderList/filters.json -#. [src.orders.views.OrderList.dateFrom] - filter by date -#. defaultMessage is: -#. Date from {date} -msgctxt "filter by date" -msgid "Date from {date}" -msgstr "" - -#: build/locale/src/orders/views/OrderList/filters.json -#. [src.orders.views.OrderList.dateIs] - filter by date -#. defaultMessage is: -#. Date is {date} -msgctxt "filter by date" -msgid "Date is {date}" -msgstr "" - -#: build/locale/src/orders/views/OrderList/filters.json -#. [src.orders.views.OrderList.dateTo] - filter by date -#. defaultMessage is: -#. Date to {date} -msgctxt "filter by date" -msgid "Date to {date}" -msgstr "" - #: build/locale/src/customers/components/CustomerAddress/CustomerAddress.json #. [src.customers.components.CustomerAddress.1224809208] #. defaultMessage is: @@ -3083,7 +3027,7 @@ msgstr "" #. [menuDetailsDeleteMenuHeader] - dialog header #. defaultMessage is: #. Delete Menu -#: build/locale/src/navigation/views/MenuList.json +#: build/locale/src/navigation/views/MenuList/MenuList.json #. [menuListDeleteMenuHeader] - dialog header #. defaultMessage is: #. Delete Menu @@ -3091,7 +3035,7 @@ msgctxt "dialog header" msgid "Delete Menu" msgstr "" -#: build/locale/src/navigation/views/MenuList.json +#: build/locale/src/navigation/views/MenuList/MenuList.json #. [menuListDeleteMenusHeader] - dialog header #. defaultMessage is: #. Delete Menus @@ -3115,8 +3059,8 @@ msgctxt "dialog header" msgid "Delete Page" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.2782958373] - dialog header +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.2782958373] - dialog header #. defaultMessage is: #. Delete Pages msgctxt "dialog header" @@ -3187,12 +3131,12 @@ msgctxt "custom search delete, dialog header" msgid "Delete Search" msgstr "" -#: build/locale/src/components/Filter/FilterSearch.json -#. [src.components.Filter.2173195312] - button +#: build/locale/src/components/FilterBar/FilterBar.json +#. [src.components.FilterBar.2173195312] - button #. defaultMessage is: #. Delete Search -#: build/locale/src/components/TableFilter/FilterChips.json -#. [src.components.TableFilter.2173195312] - button +#: build/locale/src/components/SearchBar/SearchBar.json +#. [src.components.SearchBar.2173195312] - button #. defaultMessage is: #. Delete Search msgctxt "button" @@ -3403,7 +3347,7 @@ msgctxt "description" msgid "Deleted draft orders" msgstr "" -#: build/locale/src/navigation/views/MenuList.json +#: build/locale/src/navigation/views/MenuList/MenuList.json #. [menuListDeletedMenu] #. defaultMessage is: #. Deleted menu @@ -3895,6 +3839,18 @@ msgctxt "search box label" msgid "Filter Countries" msgstr "" +#: build/locale/src/components/Filter/Filter.json +#. [src.components.Filter.996289613] - button +#. defaultMessage is: +#. Filters +msgctxt "button" +msgid "Filters" +msgstr "" + +#: build/locale/src/components/Filter/FilterContent.json +#. [src.components.Filter.996289613] +#. defaultMessage is: +#. Filters #: build/locale/src/components/FilterCard/FilterCard.json #. [src.components.FilterCard.996289613] #. defaultMessage is: @@ -3999,7 +3955,7 @@ msgstr "" #. [src.fulfilled] - order status #. defaultMessage is: #. Fulfilled -#: build/locale/src/orders/views/OrderList/filters.json +#: build/locale/src/orders/views/OrderList/messages.json #. [src.orders.views.OrderList.fulfilled] - order status #. defaultMessage is: #. Fulfilled @@ -4007,14 +3963,6 @@ msgctxt "order status" msgid "Fulfilled" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.1712863026] - order fulfillment status -#. defaultMessage is: -#. Fulfilled -msgctxt "order fulfillment status" -msgid "Fulfilled" -msgstr "" - #: build/locale/src/orders/components/OrderFulfillment/OrderFulfillment.json #. [src.orders.components.OrderFulfillment.3494686506] - section header #. defaultMessage is: @@ -4159,22 +4107,14 @@ msgctxt "description" msgid "Hidden" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.77815154] - product is hidden +#: build/locale/src/products/views/ProductList/messages.json +#. [src.products.views.ProductList.hidden] - product is hidden #. defaultMessage is: #. Hidden msgctxt "product is hidden" msgid "Hidden" msgstr "" -#: build/locale/src/products/views/ProductList/filters.json -#. [src.products.views.ProductList.hidden] - filter products by visibility -#. defaultMessage is: -#. Hidden -msgctxt "filter products by visibility" -msgid "Hidden" -msgstr "" - #: build/locale/src/intl.json #. [src.home] - home section name #. defaultMessage is: @@ -4403,22 +4343,6 @@ msgctxt "description" msgid "Languages" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.714411029] -#. defaultMessage is: -#. Last 30 Days -msgctxt "description" -msgid "Last 30 Days" -msgstr "" - -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.3053702458] -#. defaultMessage is: -#. Last 7 Days -msgctxt "description" -msgid "Last 7 Days" -msgstr "" - #: build/locale/src/intl.json #. [src.lastName] #. defaultMessage is: @@ -4427,14 +4351,6 @@ msgctxt "description" msgid "Last Name" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.2292505663] -#. defaultMessage is: -#. Last Year -msgctxt "description" -msgid "Last Year" -msgstr "" - #: build/locale/src/customers/components/CustomerStats/CustomerStats.json #. [src.customers.components.CustomerStats.1135318032] #. defaultMessage is: @@ -5651,8 +5567,8 @@ msgctxt "navigator placeholder" msgid "Order Number" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.2222765704] +#: build/locale/src/orders/views/OrderList/messages.json +#. [src.orders.views.OrderList.status] #. defaultMessage is: #. Order Status msgctxt "description" @@ -5863,22 +5779,14 @@ msgctxt "navigator notification" msgid "Our new feature to help you with your daily tasks. Run Navigator using {keyboardShortcut} shortcut." msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.1640493122] - product status +#: build/locale/src/products/views/ProductList/messages.json +#. [src.products.views.ProductList.outOfStock] - product status #. defaultMessage is: #. Out Of Stock msgctxt "product status" msgid "Out Of Stock" msgstr "" -#: build/locale/src/products/views/ProductList/filters.json -#. [src.products.views.ProductList.outOfStock] - filter products by stock -#. defaultMessage is: -#. Out of stock -msgctxt "filter products by stock" -msgid "Out of stock" -msgstr "" - #: build/locale/src/orders/components/OrderPayment/OrderPayment.json #. [src.orders.components.OrderPayment.353147224] - order payment #. defaultMessage is: @@ -5919,15 +5827,7 @@ msgctxt "description" msgid "Pages" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.210276526] - order fulfillment status -#. defaultMessage is: -#. Partially Fulfilled -msgctxt "order fulfillment status" -msgid "Partially Fulfilled" -msgstr "" - -#: build/locale/src/orders/views/OrderList/filters.json +#: build/locale/src/orders/views/OrderList/messages.json #. [src.orders.views.OrderList.partiallyFulfilled] - order status #. defaultMessage is: #. Partially Fulfilled @@ -6159,6 +6059,14 @@ msgctxt "product type" msgid "Physical" msgstr "" +#: build/locale/src/orders/views/OrderList/messages.json +#. [src.orders.views.OrderList.placed] - order +#. defaultMessage is: +#. Placed +msgctxt "order" +msgid "Placed" +msgstr "" + #: build/locale/src/staff/components/StaffPreferences/StaffPreferences.json #. [src.staff.components.StaffPreferences.2162129531] #. defaultMessage is: @@ -6303,18 +6211,6 @@ msgctxt "product unit price" msgid "Price" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.1134347598] -#. defaultMessage is: -#. Price -#: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json -#. [productVariantCreatePricesPriceInputLabel] -#. defaultMessage is: -#. Price -msgctxt "description" -msgid "Price" -msgstr "" - #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json #. [src.products.components.ProductVariantCreateDialog.1134347598] - variant price, header #. defaultMessage is: @@ -6323,6 +6219,18 @@ msgctxt "variant price, header" msgid "Price" msgstr "" +#: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json +#. [productVariantCreatePricesPriceInputLabel] +#. defaultMessage is: +#. Price +#: build/locale/src/products/views/ProductList/messages.json +#. [src.products.views.ProductList.price] +#. defaultMessage is: +#. Price +msgctxt "description" +msgid "Price" +msgstr "" + #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json #. [productVariantCreatePricesSetPricePlaceholder] - variant price #. defaultMessage is: @@ -6359,30 +6267,6 @@ msgctxt "price based shipping methods, section header" msgid "Price Based Rates" msgstr "" -#: build/locale/src/products/views/ProductList/filters.json -#. [src.products.views.ProductList.priceFrom] - filter by price -#. defaultMessage is: -#. Price from {price} -msgctxt "filter by price" -msgid "Price from {price}" -msgstr "" - -#: build/locale/src/products/views/ProductList/filters.json -#. [src.products.views.ProductList.priceIs] - filter by price -#. defaultMessage is: -#. Price is {price} -msgctxt "filter by price" -msgid "Price is {price}" -msgstr "" - -#: build/locale/src/products/views/ProductList/filters.json -#. [src.products.views.ProductList.priceTo] - filter by price -#. defaultMessage is: -#. Price to {price} -msgctxt "filter by price" -msgid "Price to {price}" -msgstr "" - #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.json #. [src.products.components.ProductVariantCreateDialog.705096461] - variant creation step #. defaultMessage is: @@ -6675,8 +6559,8 @@ msgctxt "publish collections" msgid "Publish" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.1547167026] - publish page, button +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.1547167026] - publish page, button #. defaultMessage is: #. Publish msgctxt "publish page, button" @@ -6691,8 +6575,8 @@ msgctxt "publish product, button" msgid "Publish" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.2321087286] - dialog header +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.2321087286] - dialog header #. defaultMessage is: #. Publish Pages msgctxt "dialog header" @@ -6783,16 +6667,8 @@ msgctxt "page status" msgid "Published" msgstr "" -#: build/locale/src/products/views/ProductList/filters.json -#. [src.products.views.ProductList.published] - filter products by visibility -#. defaultMessage is: -#. Published -msgctxt "filter products by visibility" -msgid "Published" -msgstr "" - -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.2543350562] - notification +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.2543350562] - notification #. defaultMessage is: #. Published pages msgctxt "notification" @@ -6847,18 +6723,6 @@ msgctxt "description" msgid "Quick Pick" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.2545228781] -#. defaultMessage is: -#. Range -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.2545228781] -#. defaultMessage is: -#. Range -msgctxt "description" -msgid "Range" -msgstr "" - #: build/locale/src/shipping/components/ShippingZoneRateDialog/ShippingZoneRateDialog.json #. [src.shipping.components.ShippingZoneRateDialog.3213611593] - shipping method #. defaultMessage is: @@ -6883,11 +6747,7 @@ msgctxt "shipping method price" msgid "Rate Price" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.2415661583] - order status -#. defaultMessage is: -#. Ready to Capture -#: build/locale/src/orders/views/OrderList/filters.json +#: build/locale/src/orders/views/OrderList/messages.json #. [src.orders.views.OrderList.readyToCapture] - order status #. defaultMessage is: #. Ready to Capture @@ -6971,8 +6831,8 @@ msgctxt "description" msgid "Removed page" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.1080715663] - notification +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.1080715663] - notification #. defaultMessage is: #. Removed pages msgctxt "notification" @@ -7139,18 +6999,6 @@ msgctxt "button" msgid "Save" msgstr "" -#: build/locale/src/components/Filter/FilterSearch.json -#. [src.components.Filter.1514415736] - button -#. defaultMessage is: -#. Save Custom Search -#: build/locale/src/components/TableFilter/FilterChips.json -#. [src.components.TableFilter.1514415736] - button -#. defaultMessage is: -#. Save Custom Search -msgctxt "button" -msgid "Save Custom Search" -msgstr "" - #: build/locale/src/components/SaveFilterTabDialog/SaveFilterTabDialog.json #. [src.components.SaveFilterTabDialog.1514415736] - save filter tab, header #. defaultMessage is: @@ -7159,6 +7007,18 @@ msgctxt "save filter tab, header" msgid "Save Custom Search" msgstr "" +#: build/locale/src/components/FilterBar/FilterBar.json +#. [src.components.FilterBar.3268463180] - button +#. defaultMessage is: +#. Save Search +#: build/locale/src/components/SearchBar/SearchBar.json +#. [src.components.SearchBar.3268463180] - button +#. defaultMessage is: +#. Save Search +msgctxt "button" +msgid "Save Search" +msgstr "" + #: build/locale/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.json #. [src.products.components.ProductVariantCreatePage.2853608829] - button #. defaultMessage is: @@ -7411,8 +7271,8 @@ msgctxt "description" msgid "Search Products" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.3550330425] +#: build/locale/src/products/components/ProductListPage/ProductListPage.json +#. [src.products.components.ProductListPage.3550330425] #. defaultMessage is: #. Search Products... msgctxt "description" @@ -7591,18 +7451,6 @@ msgctxt "webhook" msgid "Secrect Key" msgstr "" -#: build/locale/src/components/Filter/FilterContent.json -#. [src.components.Filter.2230339185] -#. defaultMessage is: -#. Select Filter... -#: build/locale/src/components/Filter/FilterElement.json -#. [src.components.Filter.2230339185] -#. defaultMessage is: -#. Select Filter... -msgctxt "description" -msgid "Select Filter..." -msgstr "" - #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.json #. [src.products.components.ProductVariantCreateDialog.2478977538] - attribute values, variant creation step #. defaultMessage is: @@ -7619,22 +7467,6 @@ msgctxt "description" msgid "Select a specific variant image from product images" msgstr "" -#: build/locale/src/orders/components/OrderListPage/OrderListPage.json -#. [src.orders.components.OrderListPage.3524904717] -#. defaultMessage is: -#. Select all orders where: -msgctxt "description" -msgid "Select all orders where:" -msgstr "" - -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.1421689426] -#. defaultMessage is: -#. Select all products where: -msgctxt "description" -msgid "Select all products where:" -msgstr "" - #: build/locale/src/components/TableHead/TableHead.json #. [src.components.TableHead.868570480] #. defaultMessage is: @@ -7995,22 +7827,6 @@ msgctxt "description" msgid "Sorry, your username and/or password are incorrect. Please try again." msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.789263812] -#. defaultMessage is: -#. Specific Date -msgctxt "description" -msgid "Specific Date" -msgstr "" - -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.2844426531] -#. defaultMessage is: -#. Specific Price -msgctxt "description" -msgid "Specific Price" -msgstr "" - #: build/locale/src/discounts/translations.json #. [src.discounts.products] - voucher discount #. defaultMessage is: @@ -8099,14 +7915,6 @@ msgctxt "order status" msgid "Status" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.1756106276] - order fulfillment status -#. defaultMessage is: -#. Status -msgctxt "order fulfillment status" -msgid "Status" -msgstr "" - #: build/locale/src/plugins/components/PluginInfo/PluginInfo.json #. [src.plugins.components.PluginInfo.1756106276] - plugin status #. defaultMessage is: @@ -8115,14 +7923,6 @@ msgctxt "plugin status" msgid "Status" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.1756106276] - product status -#. defaultMessage is: -#. Status -msgctxt "product status" -msgid "Status" -msgstr "" - #: build/locale/src/products/components/ProductVariants/ProductVariants.json #. [src.products.components.ProductVariants.1756106276] - product variant status #. defaultMessage is: @@ -8131,14 +7931,6 @@ msgctxt "product variant status" msgid "Status" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.3841616483] - product stock -#. defaultMessage is: -#. Stock -msgctxt "product stock" -msgid "Stock" -msgstr "" - #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json #. [src.products.components.ProductVariantCreateDialog.3841616483] - variant stock, header #. defaultMessage is: @@ -8171,11 +7963,11 @@ msgctxt "product variant stock, section header" msgid "Stock" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.3645081351] +#: build/locale/src/products/views/ProductList/messages.json +#. [src.products.views.ProductList.quantity] - product #. defaultMessage is: #. Stock quantity -msgctxt "description" +msgctxt "product" msgid "Stock quantity" msgstr "" @@ -9071,7 +8863,7 @@ msgstr "" #. [src.unfulfilled] - order status #. defaultMessage is: #. Unfulfilled -#: build/locale/src/orders/views/OrderList/filters.json +#: build/locale/src/orders/views/OrderList/messages.json #. [src.orders.views.OrderList.unfulfilled] - order status #. defaultMessage is: #. Unfulfilled @@ -9079,14 +8871,6 @@ msgctxt "order status" msgid "Unfulfilled" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.1751787272] - order fulfillment status -#. defaultMessage is: -#. Unfulfilled -msgctxt "order fulfillment status" -msgid "Unfulfilled" -msgstr "" - #: build/locale/src/orders/components/OrderUnfulfilledItems/OrderUnfulfilledItems.json #. [src.orders.components.OrderUnfulfilledItems.2886647373] - section header #. defaultMessage is: @@ -9111,8 +8895,8 @@ msgctxt "unpublish collections" msgid "Unpublish" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.2237014112] - unpublish page, button +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.2237014112] - unpublish page, button #. defaultMessage is: #. Unpublish msgctxt "unpublish page, button" @@ -9127,8 +8911,8 @@ msgctxt "unpublish product, button" msgid "Unpublish" msgstr "" -#: build/locale/src/pages/views/PageList.json -#. [src.pages.views.158565417] - dialog header +#: build/locale/src/pages/views/PageList/PageList.json +#. [src.pages.views.PageList.158565417] - dialog header #. defaultMessage is: #. Unpublish Pages msgctxt "dialog header" @@ -9483,8 +9267,8 @@ msgctxt "page status" msgid "Visibility" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.1459686496] - product visibility +#: build/locale/src/products/views/ProductList/messages.json +#. [src.products.views.ProductList.visibility] - product visibility #. defaultMessage is: #. Visibility msgctxt "product visibility" @@ -9507,8 +9291,8 @@ msgctxt "description" msgid "Visible" msgstr "" -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.643174786] - product is visible +#: build/locale/src/products/views/ProductList/messages.json +#. [src.products.views.ProductList.visible] - product is visible #. defaultMessage is: #. Visible msgctxt "product is visible" @@ -9751,6 +9535,22 @@ msgctxt "account status" msgid "active" msgstr "" +#: build/locale/src/components/Filter/FilterContent.json +#. [src.components.Filter.2779594451] - filter range separator +#. defaultMessage is: +#. and +msgctxt "filter range separator" +msgid "and" +msgstr "" + +#: build/locale/src/components/Filter/FilterContent.json +#. [src.components.Filter.773313536] - is filter range or value +#. defaultMessage is: +#. between +msgctxt "is filter range or value" +msgid "between" +msgstr "" + #: build/locale/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.json #. [src.productTypes.components.ProductTypeListPage.3479705616] - button #. defaultMessage is: @@ -9783,28 +9583,12 @@ msgctxt "order does not require shipping" msgid "does not apply" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.3477667254] +#: build/locale/src/components/Filter/FilterContent.json +#. [src.components.Filter.2683154806] - is filter range or value #. defaultMessage is: -#. equals -msgctxt "description" -msgid "equals" -msgstr "" - -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.3477667254] - product price -#. defaultMessage is: -#. equals -msgctxt "product price" -msgid "equals" -msgstr "" - -#: build/locale/src/components/Filter/FilterElement.json -#. [src.components.Filter.2755325844] -#. defaultMessage is: -#. from -msgctxt "description" -msgid "from" +#. equal to +msgctxt "is filter range or value" +msgid "equal to" msgstr "" #: build/locale/src/components/MoneyRange/MoneyRange.json @@ -9831,22 +9615,6 @@ msgctxt "account status" msgid "inactive" msgstr "" -#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json -#. [src.orders.components.OrderListFilter.1438173764] - date is set as -#. defaultMessage is: -#. is set as -msgctxt "date is set as" -msgid "is set as" -msgstr "" - -#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json -#. [src.products.components.ProductListFilter.1438173764] - product status is set as -#. defaultMessage is: -#. is set as -msgctxt "product status is set as" -msgid "is set as" -msgstr "" - #: build/locale/src/webhooks/components/WebhookInfo/WebhookInfo.json #. [src.webhooks.components.WebhookInfo.3809115222] - webhook secret key help text #. defaultMessage is: @@ -9887,14 +9655,6 @@ msgctxt "product" msgid "since {date}" msgstr "" -#: build/locale/src/components/Filter/FilterElement.json -#. [src.components.Filter.152217691] -#. defaultMessage is: -#. to -msgctxt "description" -msgid "to" -msgstr "" - #: build/locale/src/components/MoneyRange/MoneyRange.json #. [src.components.MoneyRange.12301532] - money #. defaultMessage is: From d3bd6a6c2212c6a2f895d77f439a2856ad5c024a Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 31 Dec 2019 15:23:11 +0100 Subject: [PATCH 18/85] Simplify code --- src/orders/views/OrderList/OrderList.tsx | 5 +++-- src/orders/views/OrderList/filters.ts | 11 ----------- src/products/views/ProductList/ProductList.tsx | 7 ++++--- src/products/views/ProductList/filters.ts | 11 ----------- src/utils/filters/filters.ts | 18 ++++++++++++++++++ 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 8b32ee357..38058b842 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -20,6 +20,7 @@ import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { getSortParams } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import { IFilter } from "@saleor/components/Filter"; +import { getFilterQueryParams } from "@saleor/utils/filters"; import OrderBulkCancelDialog from "../../components/OrderBulkCancelDialog"; import OrderListPage from "../../components/OrderListPage/OrderListPage"; import { @@ -44,7 +45,7 @@ import { getFilterVariables, saveFilterTab, OrderFilterKeys, - createFilterQueryParams + getFilterQueryParam } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -92,7 +93,7 @@ export const OrderList: React.FC = ({ params }) => { navigate( orderListUrl({ ...params, - ...createFilterQueryParams(filter), + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index 97482955c..7e8e0629a 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -164,17 +164,6 @@ export function getFilterQueryParam( }; } } -export function createFilterQueryParams( - filter: IFilter -): OrderListUrlFilters { - return filter.reduce( - (acc, filterField) => ({ - ...acc, - ...getFilterQueryParam(filterField) - }), - {} - ); -} export const { deleteFilterTab, diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 3cf59a9c5..e9386c362 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -26,6 +26,7 @@ import { ListViews } from "@saleor/types"; import { getSortUrlVariables } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import { IFilter } from "@saleor/components/Filter"; +import { getFilterQueryParams } from "@saleor/utils/filters"; import ProductListPage from "../../components/ProductListPage"; import { TypedProductBulkDeleteMutation, @@ -53,8 +54,8 @@ import { getFilterVariables, saveFilterTab, ProductFilterKeys, - createFilterQueryParams, - getFilterOpts + getFilterOpts, + getFilterQueryParam } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -107,7 +108,7 @@ export const ProductList: React.FC = ({ params }) => { navigate( productListUrl({ ...params, - ...createFilterQueryParams(filter), + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 4f9c486a5..e3fd579ed 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -176,17 +176,6 @@ export function getFilterQueryParam( }; } } -export function createFilterQueryParams( - filter: IFilter -): ProductListUrlFilters { - return filter.reduce( - (acc, filterField) => ({ - ...acc, - ...getFilterQueryParam(filterField) - }), - {} - ); -} export const { deleteFilterTab, diff --git a/src/utils/filters/filters.ts b/src/utils/filters/filters.ts index 09601718b..63456e042 100644 --- a/src/utils/filters/filters.ts +++ b/src/utils/filters/filters.ts @@ -1,3 +1,5 @@ +import { IFilterElement, IFilter } from "@saleor/components/Filter"; + function createFilterUtils< TQueryParams extends object, TFilters extends object @@ -25,4 +27,20 @@ export function dedupeFilter(array: T[]): T[] { return Array.from(new Set(array)); } +export function getFilterQueryParams< + TFilterKeys extends string, + TUrlFilters extends object +>( + filter: IFilter, + getFilterQueryParam: (filter: IFilterElement) => TUrlFilters +): TUrlFilters { + return filter.reduce( + (acc, filterField) => ({ + ...acc, + ...getFilterQueryParam(filterField) + }), + {} as TUrlFilters + ); +} + export default createFilterUtils; From 0ad80813feb779fcdb151832bba6c2b4a2114a31 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 2 Jan 2020 13:22:12 +0100 Subject: [PATCH 19/85] Add filtering to customer list --- .../CustomerListPage/CustomerListPage.tsx | 25 ++- src/customers/types.ts | 8 + src/customers/urls.ts | 6 + .../views/CustomerList/CustomerList.tsx | 45 ++++- src/customers/views/CustomerList/filter.ts | 171 ++++++++++++++++++ src/customers/views/CustomerList/messages.ts | 17 ++ .../stories/customers/CustomerListPage.tsx | 27 ++- 7 files changed, 285 insertions(+), 14 deletions(-) create mode 100644 src/customers/views/CustomerList/messages.ts diff --git a/src/customers/components/CustomerListPage/CustomerListPage.tsx b/src/customers/components/CustomerListPage/CustomerListPage.tsx index 772fa7e33..371e2ec98 100644 --- a/src/customers/components/CustomerListPage/CustomerListPage.tsx +++ b/src/customers/components/CustomerListPage/CustomerListPage.tsx @@ -5,33 +5,41 @@ 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, - SearchPageProps, TabPageProps, - SortPage + SortPage, + FilterPageProps } from "@saleor/types"; import { CustomerListUrlSortField } from "@saleor/customers/urls"; -import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; +import { + CustomerFilterKeys, + createFilterStructure +} from "@saleor/customers/views/CustomerList/filter"; +import { CustomerListFilterOpts } from "@saleor/customers/types"; +import FilterBar from "@saleor/components/FilterBar"; import CustomerList from "../CustomerList/CustomerList"; +import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; export interface CustomerListPageProps extends PageListProps, ListActions, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { customers: ListCustomers_customers_edges_node[]; } const CustomerListPage: React.FC = ({ + currencySymbol, currentTab, + filterOpts, initialSearch, onAdd, onAll, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -41,6 +49,8 @@ const CustomerListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -52,18 +62,21 @@ const CustomerListPage: React.FC = ({ - ; + moneySpent: FilterOpts; + numberOfOrders: FilterOpts; +} + export interface AddressTypeInput { city: string; cityArea?: string; diff --git a/src/customers/urls.ts b/src/customers/urls.ts index ebfb188fd..819672388 100644 --- a/src/customers/urls.ts +++ b/src/customers/urls.ts @@ -16,6 +16,12 @@ export const customerSection = "/customers/"; export const customerListPath = customerSection; export enum CustomerListUrlFiltersEnum { + joinedFrom = "joinedFrom", + joinedTo = "joinedTo", + moneySpentFrom = "moneySpentFrom", + moneySpentTo = "moneySpentTo", + numberOfOrdersFrom = "numberOfOrdersFrom", + numberOfOrdersTo = "numberOfOrdersTo", query = "query" } export type CustomerListUrlFilters = Filters; diff --git a/src/customers/views/CustomerList/CustomerList.tsx b/src/customers/views/CustomerList/CustomerList.tsx index 92a28a9d0..a2641a553 100644 --- a/src/customers/views/CustomerList/CustomerList.tsx +++ b/src/customers/views/CustomerList/CustomerList.tsx @@ -22,6 +22,9 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { IFilter } from "@saleor/components/Filter"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import useShop from "@saleor/hooks/useShop"; import CustomerListPage from "../../components/CustomerListPage"; import { TypedBulkRemoveCustomers } from "../../mutations"; import { useCustomerListQuery } from "../../queries"; @@ -29,7 +32,6 @@ import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers"; import { customerAddUrl, customerListUrl, - CustomerListUrlFilters, CustomerListUrlQueryParams, customerUrl, CustomerListUrlDialog @@ -40,7 +42,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + CustomerFilterKeys, + getFilterQueryParam, + getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -59,6 +64,7 @@ export const CustomerList: React.FC = ({ params }) => { ListViews.CUSTOMER_LIST ); const intl = useIntl(); + const shop = useShop(); const paginationState = createPaginationState(settings.rowNumber, params); const queryVariables = React.useMemo( @@ -83,17 +89,38 @@ export const CustomerList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: CustomerListUrlFilters) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( customerListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); }; + const resetFilters = () => { + reset(); + navigate( + customerListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + + const handleSearchChange = (query: string) => { + reset(); + navigate( + customerListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const [openModal, closeModal] = createDialogActionHandlers< CustomerListUrlDialog, CustomerListUrlQueryParams @@ -138,16 +165,20 @@ export const CustomerList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, customerListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( {(bulkRemoveCustomers, bulkRemoveCustomersOpts) => ( <> changeFilterField({ query })} - onAll={() => navigate(customerListUrl())} + onSearchChange={handleSearchChange} + onFilterChange={changeFilters} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/customers/views/CustomerList/filter.ts b/src/customers/views/CustomerList/filter.ts index 98e29cf77..44c238aa8 100644 --- a/src/customers/views/CustomerList/filter.ts +++ b/src/customers/views/CustomerList/filter.ts @@ -1,4 +1,12 @@ +import { IntlShape } from "react-intl"; + import { CustomerFilterInput } from "@saleor/types/globalTypes"; +import { maybe } from "@saleor/misc"; +import { + createDateField, + createNumberField +} from "@saleor/utils/filters/fields"; +import { IFilter, IFilterElement } from "@saleor/components/Filter"; import { createFilterTabUtils, createFilterUtils @@ -8,17 +16,180 @@ import { CustomerListUrlFiltersEnum, CustomerListUrlQueryParams } from "../../urls"; +import { CustomerListFilterOpts } from "../../types"; +import messages from "./messages"; export const CUSTOMER_FILTERS_KEY = "customerFilters"; +export enum CustomerFilterKeys { + joined = "joined", + moneySpent = "spent", + numberOfOrders = "orders" +} + +export function getFilterOpts( + params: CustomerListUrlFilters +): CustomerListFilterOpts { + return { + joined: { + active: maybe( + () => + [params.joinedFrom, params.joinedTo].some( + field => field !== undefined + ), + false + ), + value: { + max: maybe(() => params.joinedTo, ""), + min: maybe(() => params.joinedFrom, "") + } + }, + moneySpent: { + active: maybe( + () => + [params.moneySpentFrom, params.moneySpentTo].some( + field => field !== undefined + ), + false + ), + value: { + max: maybe(() => params.moneySpentTo, ""), + min: maybe(() => params.moneySpentFrom, "") + } + }, + numberOfOrders: { + active: maybe( + () => + [params.numberOfOrdersFrom, params.numberOfOrdersTo].some( + field => field !== undefined + ), + false + ), + value: { + max: maybe(() => params.numberOfOrdersTo, ""), + min: maybe(() => params.numberOfOrdersFrom, "") + } + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: CustomerListFilterOpts +): IFilter { + return [ + { + ...createDateField( + CustomerFilterKeys.joined, + intl.formatMessage(messages.joinDate), + opts.joined.value + ), + active: opts.joined.active + }, + { + ...createNumberField( + CustomerFilterKeys.moneySpent, + intl.formatMessage(messages.moneySpent), + opts.moneySpent.value + ), + active: opts.moneySpent.active + }, + { + ...createNumberField( + CustomerFilterKeys.numberOfOrders, + intl.formatMessage(messages.numberOfOrders), + opts.numberOfOrders.value + ), + active: opts.numberOfOrders.active + } + ]; +} + export function getFilterVariables( params: CustomerListUrlFilters ): CustomerFilterInput { return { + dateJoined: { + gte: params.joinedFrom, + lte: params.joinedTo + }, + moneySpent: { + gte: parseInt(params.moneySpentFrom, 0), + lte: parseInt(params.moneySpentTo, 0) + }, + numberOfOrders: { + gte: parseInt(params.numberOfOrdersFrom, 0), + lte: parseInt(params.numberOfOrdersTo, 0) + }, search: params.query }; } +export function getFilterQueryParam( + filter: IFilterElement +): CustomerListUrlFilters { + const { active, multiple, name, value } = filter; + + switch (name) { + case CustomerFilterKeys.joined: + if (!active) { + return { + joinedFrom: undefined, + joinedTo: undefined + }; + } + if (multiple) { + return { + joinedFrom: value[0], + joinedTo: value[1] + }; + } + + return { + joinedFrom: value[0], + joinedTo: value[0] + }; + + case CustomerFilterKeys.moneySpent: + if (!active) { + return { + moneySpentFrom: undefined, + moneySpentTo: undefined + }; + } + if (multiple) { + return { + moneySpentFrom: value[0], + moneySpentTo: value[1] + }; + } + + return { + moneySpentFrom: value[0], + moneySpentTo: value[0] + }; + + case CustomerFilterKeys.numberOfOrders: + if (!active) { + return { + numberOfOrdersFrom: undefined, + numberOfOrdersTo: undefined + }; + } + if (multiple) { + return { + numberOfOrdersFrom: value[0], + numberOfOrdersTo: value[1] + }; + } + + return { + numberOfOrdersFrom: value[0], + numberOfOrdersTo: value[0] + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/customers/views/CustomerList/messages.ts b/src/customers/views/CustomerList/messages.ts new file mode 100644 index 000000000..8ba6fd197 --- /dev/null +++ b/src/customers/views/CustomerList/messages.ts @@ -0,0 +1,17 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + joinDate: { + defaultMessage: "Join Date", + description: "customer" + }, + moneySpent: { + defaultMessage: "Money Spent", + description: "customer" + }, + numberOfOrders: { + defaultMessage: "Number of Orders" + } +}); + +export default messages; diff --git a/src/storybook/stories/customers/CustomerListPage.tsx b/src/storybook/stories/customers/CustomerListPage.tsx index 2ec0d992a..b6d1c2709 100644 --- a/src/storybook/stories/customers/CustomerListPage.tsx +++ b/src/storybook/stories/customers/CustomerListPage.tsx @@ -11,17 +11,42 @@ import { pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; const props: CustomerListPageProps = { + ...filterPageProps, ...listActionsProps, ...pageListProps.default, ...searchPageProps, ...sortPageProps, ...tabPageProps, customers: customerList, + filterOpts: { + joined: { + active: false, + value: { + max: undefined, + min: undefined + } + }, + moneySpent: { + active: false, + value: { + max: undefined, + min: undefined + } + }, + numberOfOrders: { + active: false, + value: { + max: undefined, + min: undefined + } + } + }, sort: { ...sortPageProps.sort, sort: CustomerListUrlSortField.name From 223237d0fdd3199bb0f142c3acb36831b9ca7260 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 2 Jan 2020 15:43:44 +0100 Subject: [PATCH 20/85] Update snapshots --- .../__snapshots__/Stories.test.ts.snap | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index b5a8203f5..0a8bbeb98 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -37924,8 +37924,21 @@ exports[`Storyshots Views / Customers / Customer list default 1`] = `
+
+ +
@@ -39064,8 +39077,21 @@ exports[`Storyshots Views / Customers / Customer list loading 1`] = `
+
+ +
@@ -39433,8 +39459,21 @@ exports[`Storyshots Views / Customers / Customer list no data 1`] = `
+
+ +
From b4cb44834c3fbcc67acbccefc166dfa3b7b23039 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 7 Jan 2020 14:34:45 +0100 Subject: [PATCH 21/85] Add filtering to collections --- .../CollectionListPage/CollectionListPage.tsx | 21 ++++- src/collections/types.ts | 6 ++ src/collections/urls.ts | 1 + .../views/CollectionList/CollectionList.tsx | 44 ++++++++-- .../views/CollectionList/filter.ts | 83 ++++++++++++++++++- .../views/CollectionList/messages.ts | 14 ++++ src/intl.ts | 3 + 7 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 src/collections/types.ts create mode 100644 src/collections/views/CollectionList/messages.ts diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index df72134ba..cf20510bf 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -5,34 +5,42 @@ 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 FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { ListActions, PageListProps, - SearchPageProps, + FilterPageProps, TabPageProps, SortPage } from "@saleor/types"; import { CollectionListUrlSortField } from "@saleor/collections/urls"; +import { + CollectionFilterKeys, + createFilterStructure +} from "@saleor/collections/views/CollectionList/filter"; +import { CollectionListFilterOpts } from "@saleor/collections/types"; import { CollectionList_collections_edges_node } from "../../types/CollectionList"; import CollectionList from "../CollectionList/CollectionList"; export interface CollectionListPageProps extends PageListProps, ListActions, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { collections: CollectionList_collections_edges_node[]; } const CollectionListPage: React.FC = ({ + currencySymbol, currentTab, disabled, + filterOpts, initialSearch, onAdd, onAll, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -42,6 +50,8 @@ const CollectionListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -58,18 +68,21 @@ const CollectionListPage: React.FC = ({ - ; +} diff --git a/src/collections/urls.ts b/src/collections/urls.ts index 908fc998a..d22345b8a 100644 --- a/src/collections/urls.ts +++ b/src/collections/urls.ts @@ -15,6 +15,7 @@ const collectionSectionUrl = "/collections/"; export const collectionListPath = collectionSectionUrl; export enum CollectionListUrlFiltersEnum { + status = "status", query = "query" } export type CollectionListUrlFilters = Filters; diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index f5c30df11..4cb9c6d53 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -23,6 +23,9 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { IFilter } from "@saleor/components/Filter"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import useShop from "@saleor/hooks/useShop"; import CollectionListPage from "../../components/CollectionListPage/CollectionListPage"; import { TypedCollectionBulkDelete, @@ -45,7 +48,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + CollectionFilterKeys, + getFilterQueryParam, + getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -57,6 +63,7 @@ export const CollectionList: React.FC = ({ params }) => { const navigate = useNavigator(); const notify = useNotifier(); const paginate = usePaginator(); + const shop = useShop(); const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( params.ids ); @@ -88,17 +95,38 @@ export const CollectionList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: CollectionListUrlFilters) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( collectionListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); }; + const resetFilters = () => { + reset(); + navigate( + collectionListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + + const handleSearchChange = (query: string) => { + reset(); + navigate( + collectionListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const [openModal, closeModal] = createDialogActionHandlers< CollectionListUrlDialog, CollectionListUrlQueryParams @@ -154,6 +182,7 @@ export const CollectionList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, collectionListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( @@ -162,11 +191,14 @@ export const CollectionList: React.FC = ({ params }) => { {(collectionBulkPublish, collectionBulkPublishOpts) => ( <> changeFilterField({ query })} + onSearchChange={handleSearchChange} + onFilterChange={changeFilters} onAdd={() => navigate(collectionAddUrl)} - onAll={() => navigate(collectionListUrl())} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/collections/views/CollectionList/filter.ts b/src/collections/views/CollectionList/filter.ts index 47a710945..1e09bfed7 100644 --- a/src/collections/views/CollectionList/filter.ts +++ b/src/collections/views/CollectionList/filter.ts @@ -1,24 +1,99 @@ -import { CollectionFilterInput } from "@saleor/types/globalTypes"; +import { IntlShape } from "react-intl"; + import { - createFilterTabUtils, - createFilterUtils -} from "../../../utils/filters"; + CollectionFilterInput, + CollectionPublished +} from "@saleor/types/globalTypes"; +import { IFilterElement, IFilter } from "@saleor/components/Filter"; +import { maybe, findValueInEnum } from "@saleor/misc"; +import { createOptionsField } from "@saleor/utils/filters/fields"; +import { commonMessages } from "@saleor/intl"; +import { CollectionListFilterOpts } from "../../types"; import { CollectionListUrlFilters, CollectionListUrlFiltersEnum, CollectionListUrlQueryParams } from "../../urls"; +import { + createFilterTabUtils, + createFilterUtils +} from "../../../utils/filters"; +import messages from "./messages"; export const COLLECTION_FILTERS_KEY = "collectionFilters"; +export enum CollectionFilterKeys { + status = "status" +} + +export function getFilterOpts( + params: CollectionListUrlFilters +): CollectionListFilterOpts { + return { + status: { + active: maybe(() => params.status !== undefined, false), + value: maybe(() => findValueInEnum(status, CollectionPublished)) + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: CollectionListFilterOpts +): IFilter { + return [ + { + ...createOptionsField( + CollectionFilterKeys.status, + intl.formatMessage(commonMessages.status), + [opts.status.value], + false, + [ + { + label: intl.formatMessage(messages.published), + value: CollectionPublished.PUBLISHED + }, + { + label: intl.formatMessage(messages.hidden), + value: CollectionPublished.HIDDEN + } + ] + ), + active: opts.status.active + } + ]; +} + export function getFilterVariables( params: CollectionListUrlFilters ): CollectionFilterInput { return { + published: params.status + ? findValueInEnum(params.status, CollectionPublished) + : undefined, search: params.query }; } +export function getFilterQueryParam( + filter: IFilterElement +): CollectionListUrlFilters { + const { active, name, value } = filter; + + switch (name) { + case CollectionFilterKeys.status: + if (!active) { + return { + status: undefined + }; + } + + return { + status: value[0] + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/collections/views/CollectionList/messages.ts b/src/collections/views/CollectionList/messages.ts new file mode 100644 index 000000000..3e3f6a327 --- /dev/null +++ b/src/collections/views/CollectionList/messages.ts @@ -0,0 +1,14 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + hidden: { + defaultMessage: "Hidden", + description: "collection" + }, + published: { + defaultMessage: "Published", + description: "collection" + } +}); + +export default messages; diff --git a/src/intl.ts b/src/intl.ts index fee264968..afdc1a18b 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -65,6 +65,9 @@ export const commonMessages = defineMessages({ startHour: { defaultMessage: "Start Hour" }, + status: { + defaultMessage: "Status" + }, summary: { defaultMessage: "Summary" }, From 943e227522ed6dd9d85561e4fa010a4552811cb9 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 7 Jan 2020 14:38:07 +0100 Subject: [PATCH 22/85] Update stories --- .../__snapshots__/Stories.test.ts.snap | 45 +++++++++++++++++-- .../collections/CollectionListPage.tsx | 11 ++++- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 0a8bbeb98..10081e2ab 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -26957,8 +26957,21 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
+
+ +
@@ -27540,8 +27553,21 @@ exports[`Storyshots Views / Collections / Collection list loading 1`] = `
+
+ +
@@ -27918,8 +27944,21 @@ exports[`Storyshots Views / Collections / Collection list no data 1`] = `
+
+ +
diff --git a/src/storybook/stories/collections/CollectionListPage.tsx b/src/storybook/stories/collections/CollectionListPage.tsx index 80b741a6f..b5a0c0348 100644 --- a/src/storybook/stories/collections/CollectionListPage.tsx +++ b/src/storybook/stories/collections/CollectionListPage.tsx @@ -2,6 +2,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { CollectionListUrlSortField } from "@saleor/collections/urls"; +import { CollectionPublished } from "@saleor/types/globalTypes"; import CollectionListPage, { CollectionListPageProps } from "../../../collections/components/CollectionListPage"; @@ -11,7 +12,8 @@ import { pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; @@ -20,6 +22,13 @@ const props: CollectionListPageProps = { ...pageListProps.default, ...searchPageProps, ...sortPageProps, + ...filterPageProps, + filterOpts: { + status: { + active: false, + value: CollectionPublished.PUBLISHED + } + }, sort: { ...sortPageProps.sort, sort: CollectionListUrlSortField.name From eab3d6750265da5fc415e97e3cce89a884554e92 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 7 Jan 2020 14:40:25 +0100 Subject: [PATCH 23/85] Remove unused import --- src/collections/views/CollectionList/CollectionList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 4cb9c6d53..0523c8f77 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -37,7 +37,6 @@ import { CollectionBulkPublish } from "../../types/CollectionBulkPublish"; import { collectionAddUrl, collectionListUrl, - CollectionListUrlFilters, CollectionListUrlQueryParams, collectionUrl, CollectionListUrlDialog From 04e9e13c5a0d43b86f598e59af2fda7c275a5cfd Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 7 Jan 2020 13:06:11 +0100 Subject: [PATCH 24/85] Add filters to staff member list --- .../StaffListPage/StaffListPage.tsx | 21 +++++- src/staff/types.ts | 6 ++ src/staff/urls.ts | 1 + src/staff/views/StaffList/StaffList.tsx | 39 ++++++++-- src/staff/views/StaffList/filter.ts | 75 ++++++++++++++++++- src/staff/views/StaffList/messages.ts | 18 +++++ src/storybook/stories/staff/StaffListPage.tsx | 11 ++- 7 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 src/staff/types.ts create mode 100644 src/staff/views/StaffList/messages.ts diff --git a/src/staff/components/StaffListPage/StaffListPage.tsx b/src/staff/components/StaffListPage/StaffListPage.tsx index 6cd2d1211..8182657b2 100644 --- a/src/staff/components/StaffListPage/StaffListPage.tsx +++ b/src/staff/components/StaffListPage/StaffListPage.tsx @@ -6,21 +6,26 @@ 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 FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { ListProps, - SearchPageProps, + FilterPageProps, TabPageProps, SortPage } from "@saleor/types"; import { StaffListUrlSortField } from "@saleor/staff/urls"; +import { + StaffFilterKeys, + createFilterStructure +} from "@saleor/staff/views/StaffList/filter"; +import { StaffListFilterOpts } from "@saleor/staff/types"; import { StaffList_staffUsers_edges_node } from "../../types/StaffList"; import StaffList from "../StaffList/StaffList"; export interface StaffListPageProps extends ListProps, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { staffMembers: StaffList_staffUsers_edges_node[]; @@ -29,11 +34,14 @@ export interface StaffListPageProps } const StaffListPage: React.FC = ({ + currencySymbol, currentTab, + filterOpts, initialSearch, onAdd, onAll, onBack, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -43,6 +51,8 @@ const StaffListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -57,18 +67,21 @@ const StaffListPage: React.FC = ({ - ; +} diff --git a/src/staff/urls.ts b/src/staff/urls.ts index 970299df9..35ce6c2a4 100644 --- a/src/staff/urls.ts +++ b/src/staff/urls.ts @@ -15,6 +15,7 @@ const staffSection = "/staff/"; export const staffListPath = staffSection; export enum StaffListUrlFiltersEnum { + status = "status", query = "query" } export type StaffListUrlFilters = Filters; diff --git a/src/staff/views/StaffList/StaffList.tsx b/src/staff/views/StaffList/StaffList.tsx index c4ccefadd..9dfa5be9c 100644 --- a/src/staff/views/StaffList/StaffList.tsx +++ b/src/staff/views/StaffList/StaffList.tsx @@ -23,6 +23,8 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { IFilter } from "@saleor/components/Filter"; +import { getFilterQueryParams } from "@saleor/utils/filters"; import StaffAddMemberDialog, { FormData as AddStaffMemberForm } from "../../components/StaffAddMemberDialog"; @@ -33,7 +35,6 @@ import { StaffMemberAdd } from "../../types/StaffMemberAdd"; import { staffListUrl, StaffListUrlDialog, - StaffListUrlFilters, StaffListUrlQueryParams, staffMemberDetailsUrl } from "../../urls"; @@ -43,7 +44,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + StaffFilterKeys, + getFilterQueryParam, + getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -62,6 +66,7 @@ export const StaffList: React.FC = ({ params }) => { const shop = useShop(); const paginationState = createPaginationState(settings.rowNumber, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); const queryVariables = React.useMemo( () => ({ ...paginationState, @@ -84,15 +89,32 @@ export const StaffList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: StaffListUrlFilters) => + const changeFilters = (filter: IFilter) => navigate( staffListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); + const resetFilters = () => + navigate( + staffListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + + const handleSearchChange = (query: string) => + navigate( + staffListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + const [openModal, closeModal] = createDialogActionHandlers< StaffListUrlDialog, StaffListUrlQueryParams @@ -159,10 +181,13 @@ export const StaffList: React.FC = ({ params }) => { return ( <> changeFilterField({ query })} - onAll={() => navigate(staffListUrl())} + onSearchChange={handleSearchChange} + onFilterChange={changeFilters} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/staff/views/StaffList/filter.ts b/src/staff/views/StaffList/filter.ts index 9223a0372..0adaddf51 100644 --- a/src/staff/views/StaffList/filter.ts +++ b/src/staff/views/StaffList/filter.ts @@ -1,4 +1,9 @@ -import { StaffUserInput } from "@saleor/types/globalTypes"; +import { IntlShape } from "react-intl"; + +import { StaffUserInput, StaffMemberStatus } from "@saleor/types/globalTypes"; +import { maybe, findValueInEnum } from "@saleor/misc"; +import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { createOptionsField } from "@saleor/utils/filters/fields"; import { createFilterTabUtils, createFilterUtils @@ -8,17 +13,83 @@ import { StaffListUrlFiltersEnum, StaffListUrlQueryParams } from "../../urls"; +import { StaffListFilterOpts } from "../../types"; +import messages from "./messages"; export const STAFF_FILTERS_KEY = "staffFilters"; +export enum StaffFilterKeys { + status = "status" +} + +export function getFilterOpts( + params: StaffListUrlFilters +): StaffListFilterOpts { + return { + status: { + active: maybe(() => params.status !== undefined, false), + value: maybe(() => findValueInEnum(params.status, StaffMemberStatus)) + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: StaffListFilterOpts +): IFilter { + return [ + { + ...createOptionsField( + StaffFilterKeys.status, + intl.formatMessage(messages.status), + [opts.status.value], + false, + [ + { + label: intl.formatMessage(messages.active), + value: StaffMemberStatus.ACTIVE + }, + { + label: intl.formatMessage(messages.deactivated), + value: StaffMemberStatus.DEACTIVATED + } + ] + ), + active: opts.status.active + } + ]; +} + export function getFilterVariables( params: StaffListUrlFilters ): StaffUserInput { return { - search: params.query + search: params.query, + status: params.status + ? findValueInEnum(params.status, StaffMemberStatus) + : null }; } +export function getFilterQueryParam( + filter: IFilterElement +): StaffListUrlFilters { + const { active, name, value } = filter; + + switch (name) { + case StaffFilterKeys.status: + if (!active) { + return { + status: undefined + }; + } + + return { + status: value[0] + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/staff/views/StaffList/messages.ts b/src/staff/views/StaffList/messages.ts new file mode 100644 index 000000000..a6d6f38bb --- /dev/null +++ b/src/staff/views/StaffList/messages.ts @@ -0,0 +1,18 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "staff member's account" + }, + deactivated: { + defaultMessage: "Deactivated", + description: "staff member's account" + }, + status: { + defaultMessage: "Status", + description: "staff member's account" + } +}); + +export default messages; diff --git a/src/storybook/stories/staff/StaffListPage.tsx b/src/storybook/stories/staff/StaffListPage.tsx index 1c3d8c1cc..2ec0552c1 100644 --- a/src/storybook/stories/staff/StaffListPage.tsx +++ b/src/storybook/stories/staff/StaffListPage.tsx @@ -2,11 +2,13 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { StaffListUrlSortField } from "@saleor/staff/urls"; +import { StaffMemberStatus } from "@saleor/types/globalTypes"; import { pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "../../../fixtures"; import StaffListPage, { StaffListPageProps @@ -19,6 +21,13 @@ const props: StaffListPageProps = { ...searchPageProps, ...sortPageProps, ...tabPageProps, + ...filterPageProps, + filterOpts: { + status: { + active: false, + value: StaffMemberStatus.ACTIVE + } + }, onAdd: undefined, onBack: () => undefined, sort: { From 3125689ebd1344a4bebb7a42e08dda23d8519d06 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 7 Jan 2020 13:12:45 +0100 Subject: [PATCH 25/85] Update snapshots --- .../__snapshots__/Stories.test.ts.snap | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 10081e2ab..618992f20 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -121171,8 +121171,21 @@ exports[`Storyshots Views / Staff / Staff members default 1`] = `
+
+ +
@@ -121837,8 +121850,21 @@ exports[`Storyshots Views / Staff / Staff members when loading 1`] = `
+
+ +
From 1588fe7a881bb48a2ddad10779c4a53c2779e450 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 7 Jan 2020 16:06:56 +0100 Subject: [PATCH 26/85] Add filtering to product types --- .../ProductTypeListPage.tsx | 21 +++- src/productTypes/types.ts | 10 ++ src/productTypes/urls.ts | 2 + .../views/ProductTypeList/ProductTypeList.tsx | 45 +++++-- .../views/ProductTypeList/filter.ts | 118 +++++++++++++++++- .../views/ProductTypeList/messages.ts | 22 ++++ .../productTypes/ProductTypeListPage.tsx | 18 ++- 7 files changed, 223 insertions(+), 13 deletions(-) create mode 100644 src/productTypes/types.ts create mode 100644 src/productTypes/views/ProductTypeList/messages.ts diff --git a/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx b/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx index 0061de667..af9996645 100644 --- a/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx +++ b/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx @@ -6,13 +6,18 @@ 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 FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls"; +import { + ProductTypeFilterKeys, + createFilterStructure +} from "@saleor/productTypes/views/ProductTypeList/filter"; +import { ProductTypeListFilterOpts } from "@saleor/productTypes/types"; import { ListActions, PageListProps, - SearchPageProps, + FilterPageProps, TabPageProps, SortPage } from "../../../types"; @@ -22,7 +27,7 @@ import ProductTypeList from "../ProductTypeList"; export interface ProductTypeListPageProps extends PageListProps, ListActions, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { productTypes: ProductTypeList_productTypes_edges_node[]; @@ -30,11 +35,14 @@ export interface ProductTypeListPageProps } const ProductTypeListPage: React.FC = ({ + currencySymbol, currentTab, + filterOpts, initialSearch, onAdd, onAll, onBack, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -44,6 +52,8 @@ const ProductTypeListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -58,18 +68,21 @@ const ProductTypeListPage: React.FC = ({ - ; + type: FilterOpts; +} diff --git a/src/productTypes/urls.ts b/src/productTypes/urls.ts index bf295d6ae..5905a7b04 100644 --- a/src/productTypes/urls.ts +++ b/src/productTypes/urls.ts @@ -16,6 +16,8 @@ const productTypeSection = "/product-types/"; export const productTypeListPath = productTypeSection; export enum ProductTypeListUrlFiltersEnum { + configurable = "configurable", + type = "type", query = "query" } export type ProductTypeListUrlFilters = Filters; diff --git a/src/productTypes/views/ProductTypeList/ProductTypeList.tsx b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx index 92c73c7e2..bd901703b 100644 --- a/src/productTypes/views/ProductTypeList/ProductTypeList.tsx +++ b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx @@ -21,6 +21,9 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { IFilter } from "@saleor/components/Filter"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import useShop from "@saleor/hooks/useShop"; import { configurationMenuUrl } from "../../../configuration"; import { maybe } from "../../../misc"; import ProductTypeListPage from "../../components/ProductTypeListPage"; @@ -31,7 +34,6 @@ import { productTypeAddUrl, productTypeListUrl, ProductTypeListUrlDialog, - ProductTypeListUrlFilters, ProductTypeListUrlQueryParams, productTypeUrl } from "../../urls"; @@ -41,7 +43,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + ProductTypeFilterKeys, + getFilterQueryParam, + getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -53,6 +58,7 @@ export const ProductTypeList: React.FC = ({ params }) => { const navigate = useNavigator(); const notify = useNotifier(); const paginate = usePaginator(); + const shop = useShop(); const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( params.ids ); @@ -82,17 +88,38 @@ export const ProductTypeList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: ProductTypeListUrlFilters) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( productTypeListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); }; + const resetFilters = () => { + reset(); + navigate( + productTypeListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + + const handleSearchChange = (query: string) => { + reset(); + navigate( + productTypeListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const [openModal, closeModal] = createDialogActionHandlers< ProductTypeListUrlDialog, ProductTypeListUrlQueryParams @@ -143,6 +170,7 @@ export const ProductTypeList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, productTypeListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( = ({ params }) => { return ( <> changeFilterField({ query })} - onAll={() => navigate(productTypeListUrl())} + onSearchChange={handleSearchChange} + onFilterChange={changeFilters} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/productTypes/views/ProductTypeList/filter.ts b/src/productTypes/views/ProductTypeList/filter.ts index fffcd0cd8..8d542c374 100644 --- a/src/productTypes/views/ProductTypeList/filter.ts +++ b/src/productTypes/views/ProductTypeList/filter.ts @@ -1,4 +1,14 @@ -import { ProductTypeFilterInput } from "@saleor/types/globalTypes"; +import { IntlShape } from "react-intl"; + +import { + ProductTypeFilterInput, + ProductTypeConfigurable, + ProductTypeEnum +} from "@saleor/types/globalTypes"; +import { IFilterElement, IFilter } from "@saleor/components/Filter"; +import { maybe, findValueInEnum } from "@saleor/misc"; +import { createOptionsField } from "@saleor/utils/filters/fields"; +import { commonMessages } from "@saleor/intl"; import { createFilterTabUtils, createFilterUtils @@ -8,17 +18,123 @@ import { ProductTypeListUrlFiltersEnum, ProductTypeListUrlQueryParams } from "../../urls"; +import { ProductTypeListFilterOpts } from "../../types"; +import messages from "./messages"; export const PRODUCT_TYPE_FILTERS_KEY = "productTypeFilters"; +export enum ProductTypeFilterKeys { + configurable = "configurable", + type = "type" +} + +export function getFilterOpts( + params: ProductTypeListUrlFilters +): ProductTypeListFilterOpts { + return { + configurable: { + active: !!maybe(() => params.configurable), + value: maybe(() => + findValueInEnum(params.configurable, ProductTypeConfigurable) + ) + }, + type: { + active: !!maybe(() => params.type), + value: maybe(() => findValueInEnum(params.type, ProductTypeEnum)) + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: ProductTypeListFilterOpts +): IFilter { + return [ + { + ...createOptionsField( + ProductTypeFilterKeys.configurable, + intl.formatMessage(messages.configurable), + [opts.configurable.value], + false, + [ + { + label: intl.formatMessage(commonMessages.yes), + value: ProductTypeConfigurable.CONFIGURABLE + }, + { + label: intl.formatMessage(commonMessages.no), + value: ProductTypeConfigurable.SIMPLE + } + ] + ), + active: opts.configurable.active + }, + { + ...createOptionsField( + ProductTypeFilterKeys.type, + intl.formatMessage(messages.type), + [opts.type.value], + false, + [ + { + label: intl.formatMessage(messages.digital), + value: ProductTypeEnum.DIGITAL + }, + { + label: intl.formatMessage(messages.shippable), + value: ProductTypeEnum.SHIPPABLE + } + ] + ), + active: opts.type.active + } + ]; +} + export function getFilterVariables( params: ProductTypeListUrlFilters ): ProductTypeFilterInput { return { + configurable: params.configurable + ? findValueInEnum(params.configurable, ProductTypeConfigurable) + : undefined, + productType: params.type + ? findValueInEnum(params.type, ProductTypeEnum) + : undefined, search: params.query }; } +export function getFilterQueryParam( + filter: IFilterElement +): ProductTypeListUrlFilters { + const { active, name, value } = filter; + + switch (name) { + case ProductTypeFilterKeys.configurable: + if (!active) { + return { + configurable: undefined + }; + } + + return { + configurable: value[0] + }; + + case ProductTypeFilterKeys.type: + if (!active) { + return { + type: undefined + }; + } + + return { + type: value[0] + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/productTypes/views/ProductTypeList/messages.ts b/src/productTypes/views/ProductTypeList/messages.ts new file mode 100644 index 000000000..e5b3e3744 --- /dev/null +++ b/src/productTypes/views/ProductTypeList/messages.ts @@ -0,0 +1,22 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + configurable: { + defaultMessage: "Configurable", + description: "product type" + }, + digital: { + defaultMessage: "Digital", + description: "product" + }, + shippable: { + defaultMessage: "Shippable", + description: "product" + }, + type: { + defaultMessage: "Type", + description: "product type is digital or physical" + } +}); + +export default messages; diff --git a/src/storybook/stories/productTypes/ProductTypeListPage.tsx b/src/storybook/stories/productTypes/ProductTypeListPage.tsx index 3a339f83b..e4c3fa604 100644 --- a/src/storybook/stories/productTypes/ProductTypeListPage.tsx +++ b/src/storybook/stories/productTypes/ProductTypeListPage.tsx @@ -2,12 +2,17 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls"; +import { + ProductTypeConfigurable, + ProductTypeEnum +} from "@saleor/types/globalTypes"; import { listActionsProps, pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "../../../fixtures"; import ProductTypeListPage, { ProductTypeListPageProps @@ -20,6 +25,17 @@ const props: ProductTypeListPageProps = { ...pageListProps.default, ...searchPageProps, ...sortPageProps, + ...filterPageProps, + filterOpts: { + configurable: { + active: false, + value: ProductTypeConfigurable.CONFIGURABLE + }, + type: { + active: false, + value: ProductTypeEnum.SHIPPABLE + } + }, sort: { ...sortPageProps.sort, sort: ProductTypeListUrlSortField.name From 890a27ae68492488af5ab5d7e9fed71cd16558b6 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 7 Jan 2020 16:51:58 +0100 Subject: [PATCH 27/85] Update snapshots --- .../__snapshots__/Stories.test.ts.snap | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 618992f20..d57cbab08 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -84502,8 +84502,21 @@ exports[`Storyshots Views / Product types / Product types list default 1`] = `
+
+ +
@@ -85007,8 +85020,21 @@ exports[`Storyshots Views / Product types / Product types list loading 1`] = `
+
+ +
@@ -85339,8 +85365,21 @@ exports[`Storyshots Views / Product types / Product types list no data 1`] = `
+
+ +
From f9d0c22531d205cf2530ef59390c89b5568e846a Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 3 Jan 2020 14:21:58 +0100 Subject: [PATCH 28/85] Add filtering to sale list --- src/components/Filter/types.ts | 1 + .../components/SaleListPage/SaleListPage.tsx | 23 ++- src/discounts/types.ts | 12 ++ src/discounts/urls.ts | 12 +- src/discounts/views/SaleList/SaleList.tsx | 43 ++++- src/discounts/views/SaleList/filter.ts | 176 +++++++++++++++++- src/discounts/views/SaleList/messages.ts | 37 ++++ .../stories/discounts/SaleListPage.tsx | 27 ++- 8 files changed, 311 insertions(+), 20 deletions(-) create mode 100644 src/discounts/views/SaleList/messages.ts diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index 1f8b4d7ea..3ed9beb05 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -3,6 +3,7 @@ import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; export enum FieldType { date, + dateTime, number, price, options, diff --git a/src/discounts/components/SaleListPage/SaleListPage.tsx b/src/discounts/components/SaleListPage/SaleListPage.tsx index f7f5754fd..e539cfbdf 100644 --- a/src/discounts/components/SaleListPage/SaleListPage.tsx +++ b/src/discounts/components/SaleListPage/SaleListPage.tsx @@ -5,23 +5,28 @@ 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 FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { ListActions, PageListProps, - SearchPageProps, TabPageProps, - SortPage + SortPage, + FilterPageProps } from "@saleor/types"; import { SaleListUrlSortField } from "@saleor/discounts/urls"; +import { + SaleFilterKeys, + createFilterStructure +} from "@saleor/discounts/views/SaleList/filter"; +import { SaleListFilterOpts } from "@saleor/discounts/types"; import { SaleList_sales_edges_node } from "../../types/SaleList"; import SaleList from "../SaleList"; export interface SaleListPageProps extends PageListProps, ListActions, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { defaultCurrency: string; @@ -29,10 +34,13 @@ export interface SaleListPageProps } const SaleListPage: React.FC = ({ + currencySymbol, currentTab, + filterOpts, initialSearch, onAdd, onAll, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -42,6 +50,8 @@ const SaleListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -50,18 +60,21 @@ const SaleListPage: React.FC = ({ - ; + started: FilterOpts; + status: FilterOpts; +} + export enum RequirementsPicker { ORDER = "ORDER", ITEM = "ITEM", diff --git a/src/discounts/urls.ts b/src/discounts/urls.ts index efdd37f9c..7fe025069 100644 --- a/src/discounts/urls.ts +++ b/src/discounts/urls.ts @@ -8,7 +8,8 @@ import { Filters, Pagination, TabActionDialog, - Sort + Sort, + FiltersWithMultipleValues } from "../types"; import { SaleDetailsPageTab } from "./components/SaleDetailsPage"; import { VoucherDetailsPageTab } from "./components/VoucherDetailsPage"; @@ -18,9 +19,16 @@ export const discountSection = "/discounts/"; export const saleSection = urlJoin(discountSection, "sales"); export const saleListPath = saleSection; export enum SaleListUrlFiltersEnum { + type = "type", + startedFrom = "startedFrom", + startedTo = "startedTo", query = "query" } -export type SaleListUrlFilters = Filters; +export enum SaleListUrlFiltersWithMultipleValues { + status = "status" +} +export type SaleListUrlFilters = Filters & + FiltersWithMultipleValues; export type SaleListUrlDialog = "remove" | TabActionDialog; export enum SaleListUrlSortField { name = "name", diff --git a/src/discounts/views/SaleList/SaleList.tsx b/src/discounts/views/SaleList/SaleList.tsx index b068377ec..16d6d33b8 100644 --- a/src/discounts/views/SaleList/SaleList.tsx +++ b/src/discounts/views/SaleList/SaleList.tsx @@ -24,13 +24,14 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { IFilter } from "@saleor/components/Filter"; +import { getFilterQueryParams } from "@saleor/utils/filters"; import SaleListPage from "../../components/SaleListPage"; import { TypedSaleBulkDelete } from "../../mutations"; import { useSaleListQuery } from "../../queries"; import { SaleBulkDelete } from "../../types/SaleBulkDelete"; import { saleAddUrl, - SaleListUrlFilters, SaleListUrlQueryParams, saleUrl, saleListUrl, @@ -42,7 +43,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + SaleFilterKeys, + getFilterQueryParam, + getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -86,17 +90,38 @@ export const SaleList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: SaleListUrlFilters) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( saleListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); }; + const resetFilters = () => { + reset(); + navigate( + saleListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + + const handleSearchChange = (query: string) => { + reset(); + navigate( + saleListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const [openModal, closeModal] = createDialogActionHandlers< SaleListUrlDialog, SaleListUrlQueryParams @@ -143,6 +168,7 @@ export const SaleList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, saleListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( @@ -158,10 +184,13 @@ export const SaleList: React.FC = ({ params }) => { <> changeFilterField({ query })} - onAll={() => navigate(saleListUrl())} + onSearchChange={handleSearchChange} + onFilterChange={filter => changeFilters(filter)} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/discounts/views/SaleList/filter.ts b/src/discounts/views/SaleList/filter.ts index 69362c3d3..c7bae066b 100644 --- a/src/discounts/views/SaleList/filter.ts +++ b/src/discounts/views/SaleList/filter.ts @@ -1,24 +1,194 @@ -import { SaleFilterInput } from "@saleor/types/globalTypes"; +import { IntlShape } from "react-intl"; + +import { + SaleFilterInput, + DiscountStatusEnum, + DiscountValueTypeEnum +} from "@saleor/types/globalTypes"; +import { maybe, findValueInEnum, joinDateTime } from "@saleor/misc"; +import { SaleListFilterOpts } from "@saleor/discounts/types"; +import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { + createDateField, + createOptionsField +} from "@saleor/utils/filters/fields"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + dedupeFilter } from "../../../utils/filters"; import { SaleListUrlFilters, SaleListUrlFiltersEnum, SaleListUrlQueryParams } from "../../urls"; +import messages from "./messages"; export const SALE_FILTERS_KEY = "saleFilters"; +export enum SaleFilterKeys { + saleType = "saleType", + started = "started", + status = "status" +} + +export function getFilterOpts(params: SaleListUrlFilters): SaleListFilterOpts { + return { + saleType: { + active: !!maybe(() => params.type), + value: maybe(() => findValueInEnum(params.type, DiscountValueTypeEnum)) + }, + started: { + active: maybe( + () => + [params.startedFrom, params.startedTo].some( + field => field !== undefined + ), + false + ), + value: { + max: maybe(() => params.startedTo, ""), + min: maybe(() => params.startedFrom, "") + } + }, + status: { + active: !!maybe(() => params.status), + value: maybe( + () => + dedupeFilter( + params.status.map(status => + findValueInEnum(status, DiscountStatusEnum) + ) + ), + [] + ) + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: SaleListFilterOpts +): IFilter { + return [ + { + ...createDateField( + SaleFilterKeys.started, + intl.formatMessage(messages.started), + opts.started.value + ), + active: opts.started.active + }, + { + ...createOptionsField( + SaleFilterKeys.status, + intl.formatMessage(messages.status), + opts.status.value, + true, + [ + { + label: intl.formatMessage(messages.active), + value: DiscountStatusEnum.ACTIVE + }, + { + label: intl.formatMessage(messages.expired), + value: DiscountStatusEnum.EXPIRED + }, + { + label: intl.formatMessage(messages.scheduled), + value: DiscountStatusEnum.SCHEDULED + } + ] + ), + active: opts.status.active + }, + { + ...createOptionsField( + SaleFilterKeys.saleType, + intl.formatMessage(messages.type), + [opts.saleType.value], + false, + [ + { + label: intl.formatMessage(messages.fixed), + value: DiscountValueTypeEnum.FIXED + }, + { + label: intl.formatMessage(messages.percentage), + value: DiscountValueTypeEnum.PERCENTAGE + } + ] + ), + active: opts.saleType.active + } + ]; +} + export function getFilterVariables( params: SaleListUrlFilters ): SaleFilterInput { return { - search: params.query + saleType: + params.type && findValueInEnum(params.type, DiscountValueTypeEnum), + search: params.query, + started: { + gte: joinDateTime(params.startedFrom), + lte: joinDateTime(params.startedTo) + }, + status: + params.status && + params.status.map(status => findValueInEnum(status, DiscountStatusEnum)) }; } +export function getFilterQueryParam( + filter: IFilterElement +): SaleListUrlFilters { + const { active, multiple, name, value } = filter; + + switch (name) { + case SaleFilterKeys.saleType: + if (!active) { + return { + type: undefined + }; + } + + return { + type: findValueInEnum(value[0], DiscountValueTypeEnum) + }; + + case SaleFilterKeys.started: + if (!active) { + return { + startedFrom: undefined, + startedTo: undefined + }; + } + if (multiple) { + return { + startedFrom: value[0], + startedTo: value[1] + }; + } + + return { + startedFrom: value[0], + startedTo: value[0] + }; + + case SaleFilterKeys.status: + if (!active) { + return { + status: undefined + }; + } + return { + status: value.map(val => findValueInEnum(val, DiscountStatusEnum)) + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/discounts/views/SaleList/messages.ts b/src/discounts/views/SaleList/messages.ts new file mode 100644 index 000000000..70cb66cc9 --- /dev/null +++ b/src/discounts/views/SaleList/messages.ts @@ -0,0 +1,37 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "sale status" + }, + expired: { + defaultMessage: "Expired", + description: "sale status" + }, + fixed: { + defaultMessage: "Fixed amount", + description: "discount type" + }, + percentage: { + defaultMessage: "Percentage", + description: "discount type" + }, + scheduled: { + defaultMessage: "Scheduled", + description: "sale status" + }, + started: { + defaultMessage: "Started", + description: "sale start date" + }, + status: { + defaultMessage: "Status", + description: "sale status" + }, + type: { + defaultMessage: "Discount Type" + } +}); + +export default messages; diff --git a/src/storybook/stories/discounts/SaleListPage.tsx b/src/storybook/stories/discounts/SaleListPage.tsx index 07eb24509..7dfd58feb 100644 --- a/src/storybook/stories/discounts/SaleListPage.tsx +++ b/src/storybook/stories/discounts/SaleListPage.tsx @@ -2,6 +2,10 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { SaleListUrlSortField } from "@saleor/discounts/urls"; +import { + DiscountValueTypeEnum, + DiscountStatusEnum +} from "@saleor/types/globalTypes"; import SaleListPage, { SaleListPageProps } from "../../../discounts/components/SaleListPage"; @@ -9,19 +13,36 @@ import { saleList } from "../../../discounts/fixtures"; import { listActionsProps, pageListProps, - searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; const props: SaleListPageProps = { ...listActionsProps, ...pageListProps.default, - ...searchPageProps, + ...filterPageProps, ...sortPageProps, ...tabPageProps, defaultCurrency: "USD", + filterOpts: { + saleType: { + active: false, + value: DiscountValueTypeEnum.FIXED + }, + started: { + active: false, + value: { + max: undefined, + min: undefined + } + }, + status: { + active: false, + value: [DiscountStatusEnum.ACTIVE] + } + }, sales: saleList, sort: { ...sortPageProps.sort, From 7820c65050839c80b906e03431f07f1a65368489 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 3 Jan 2020 16:17:51 +0100 Subject: [PATCH 29/85] Add filters to voucher list --- .../VoucherListPage/VoucherListPage.tsx | 23 +- src/discounts/types.ts | 10 +- src/discounts/urls.ts | 11 +- .../views/VoucherList/VoucherList.tsx | 43 +++- src/discounts/views/VoucherList/filter.ts | 234 +++++++++++++++++- src/discounts/views/VoucherList/messages.ts | 41 +++ .../stories/discounts/VoucherListPage.tsx | 32 ++- 7 files changed, 375 insertions(+), 19 deletions(-) create mode 100644 src/discounts/views/VoucherList/messages.ts diff --git a/src/discounts/components/VoucherListPage/VoucherListPage.tsx b/src/discounts/components/VoucherListPage/VoucherListPage.tsx index bf7cc6079..947e11ffa 100644 --- a/src/discounts/components/VoucherListPage/VoucherListPage.tsx +++ b/src/discounts/components/VoucherListPage/VoucherListPage.tsx @@ -5,23 +5,28 @@ 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 FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { ListActions, PageListProps, - SearchPageProps, TabPageProps, - SortPage + SortPage, + FilterPageProps } from "@saleor/types"; import { VoucherListUrlSortField } from "@saleor/discounts/urls"; +import { + createFilterStructure, + VoucherFilterKeys +} from "@saleor/discounts/views/VoucherList/filter"; +import { VoucherListFilterOpts } from "@saleor/discounts/types"; import { VoucherList_vouchers_edges_node } from "../../types/VoucherList"; import VoucherList from "../VoucherList"; export interface VoucherListPageProps extends PageListProps, ListActions, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { defaultCurrency: string; @@ -29,10 +34,13 @@ export interface VoucherListPageProps } const VoucherListPage: React.FC = ({ + currencySymbol, currentTab, + filterOpts, initialSearch, onAdd, onAll, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -42,6 +50,8 @@ const VoucherListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -53,18 +63,21 @@ const VoucherListPage: React.FC = ({ - ; } +export interface VoucherListFilterOpts { + saleType: FilterOpts; + started: FilterOpts; + status: FilterOpts; + timesUsed: FilterOpts; +} + export enum RequirementsPicker { ORDER = "ORDER", ITEM = "ITEM", diff --git a/src/discounts/urls.ts b/src/discounts/urls.ts index 7fe025069..186e9a722 100644 --- a/src/discounts/urls.ts +++ b/src/discounts/urls.ts @@ -67,9 +67,18 @@ export const saleAddUrl = saleAddPath; export const voucherSection = urlJoin(discountSection, "vouchers"); export const voucherListPath = voucherSection; export enum VoucherListUrlFiltersEnum { + startedFrom = "startedFrom", + startedTo = "startedTo", + timesUsedFrom = "timesUsedFrom", + timesUsedTo = "timesUsedTo", query = "query" } -export type VoucherListUrlFilters = Filters; +export enum VoucherListUrlFiltersWithMultipleValues { + status = "status", + type = "type" +} +export type VoucherListUrlFilters = Filters & + FiltersWithMultipleValues; export type VoucherListUrlDialog = "remove" | TabActionDialog; export enum VoucherListUrlSortField { code = "code", diff --git a/src/discounts/views/VoucherList/VoucherList.tsx b/src/discounts/views/VoucherList/VoucherList.tsx index 522a3ac74..f9931f941 100644 --- a/src/discounts/views/VoucherList/VoucherList.tsx +++ b/src/discounts/views/VoucherList/VoucherList.tsx @@ -24,6 +24,8 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { IFilter } from "@saleor/components/Filter"; import VoucherListPage from "../../components/VoucherListPage"; import { TypedVoucherBulkDelete } from "../../mutations"; import { useVoucherListQuery } from "../../queries"; @@ -31,7 +33,6 @@ import { VoucherBulkDelete } from "../../types/VoucherBulkDelete"; import { voucherAddUrl, voucherListUrl, - VoucherListUrlFilters, VoucherListUrlQueryParams, voucherUrl, VoucherListUrlDialog @@ -42,7 +43,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + getFilterQueryParam, + VoucherFilterKeys, + getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -86,17 +90,38 @@ export const VoucherList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: VoucherListUrlFilters) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( voucherListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); }; + const resetFilters = () => { + reset(); + navigate( + voucherListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + + const handleSearchChange = (query: string) => { + reset(); + navigate( + voucherListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const [openModal, closeModal] = createDialogActionHandlers< VoucherListUrlDialog, VoucherListUrlQueryParams @@ -143,6 +168,7 @@ export const VoucherList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, voucherListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( @@ -158,10 +184,13 @@ export const VoucherList: React.FC = ({ params }) => { <> changeFilterField({ query })} - onAll={() => navigate(voucherListUrl())} + onSearchChange={handleSearchChange} + onFilterChange={filter => changeFilters(filter)} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/discounts/views/VoucherList/filter.ts b/src/discounts/views/VoucherList/filter.ts index 6f5d7ce57..acf149b80 100644 --- a/src/discounts/views/VoucherList/filter.ts +++ b/src/discounts/views/VoucherList/filter.ts @@ -1,24 +1,250 @@ -import { VoucherFilterInput } from "@saleor/types/globalTypes"; +import { IntlShape } from "react-intl"; + +import { + VoucherFilterInput, + DiscountStatusEnum, + DiscountValueTypeEnum, + VoucherDiscountType +} from "@saleor/types/globalTypes"; +import { maybe, findValueInEnum, joinDateTime } from "@saleor/misc"; +import { VoucherListFilterOpts } from "@saleor/discounts/types"; +import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { + createDateField, + createOptionsField, + createNumberField +} from "@saleor/utils/filters/fields"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + dedupeFilter } from "../../../utils/filters"; import { VoucherListUrlFilters, VoucherListUrlFiltersEnum, VoucherListUrlQueryParams } from "../../urls"; +import messages from "./messages"; -export const VOUCHER_FILTERS_KEY = "VoucherFilters"; +export const VOUCHER_FILTERS_KEY = "voucherFilters"; + +export enum VoucherFilterKeys { + saleType = "saleType", + started = "started", + status = "status", + timesUsed = "timesUsed" +} + +export function getFilterOpts( + params: VoucherListUrlFilters +): VoucherListFilterOpts { + return { + saleType: { + active: !!maybe(() => params.type), + value: maybe( + () => + dedupeFilter( + params.type.map(type => findValueInEnum(type, VoucherDiscountType)) + ), + [] + ) + }, + started: { + active: maybe( + () => + [params.startedFrom, params.startedTo].some( + field => field !== undefined + ), + false + ), + value: { + max: maybe(() => params.startedTo, ""), + min: maybe(() => params.startedFrom, "") + } + }, + status: { + active: !!maybe(() => params.status), + value: maybe( + () => + dedupeFilter( + params.status.map(status => + findValueInEnum(status, DiscountStatusEnum) + ) + ), + [] + ) + }, + timesUsed: { + active: maybe( + () => + [params.timesUsedFrom, params.timesUsedTo].some( + field => field !== undefined + ), + false + ), + value: { + max: maybe(() => params.timesUsedTo, ""), + min: maybe(() => params.timesUsedFrom, "") + } + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: VoucherListFilterOpts +): IFilter { + return [ + { + ...createDateField( + VoucherFilterKeys.started, + intl.formatMessage(messages.started), + opts.started.value + ), + active: opts.started.active + }, + { + ...createNumberField( + VoucherFilterKeys.timesUsed, + intl.formatMessage(messages.timesUsed), + opts.timesUsed.value + ), + active: opts.timesUsed.active + }, + { + ...createOptionsField( + VoucherFilterKeys.status, + intl.formatMessage(messages.status), + opts.status.value, + true, + [ + { + label: intl.formatMessage(messages.active), + value: DiscountStatusEnum.ACTIVE + }, + { + label: intl.formatMessage(messages.expired), + value: DiscountStatusEnum.EXPIRED + }, + { + label: intl.formatMessage(messages.scheduled), + value: DiscountStatusEnum.SCHEDULED + } + ] + ), + active: opts.status.active + }, + { + ...createOptionsField( + VoucherFilterKeys.saleType, + intl.formatMessage(messages.type), + opts.saleType.value, + false, + [ + { + label: intl.formatMessage(messages.fixed), + value: DiscountValueTypeEnum.FIXED + }, + { + label: intl.formatMessage(messages.percentage), + value: DiscountValueTypeEnum.PERCENTAGE + } + ] + ), + active: opts.saleType.active + } + ]; +} export function getFilterVariables( params: VoucherListUrlFilters ): VoucherFilterInput { return { - search: params.query + discountType: + params.type && + params.type.map(type => findValueInEnum(type, VoucherDiscountType)), + search: params.query, + started: { + gte: joinDateTime(params.startedFrom), + lte: joinDateTime(params.startedTo) + }, + status: + params.status && + params.status.map(status => findValueInEnum(status, DiscountStatusEnum)), + timesUsed: { + gte: parseInt(params.timesUsedFrom, 0), + lte: parseInt(params.timesUsedTo, 0) + } }; } +export function getFilterQueryParam( + filter: IFilterElement +): VoucherListUrlFilters { + const { active, multiple, name, value } = filter; + + switch (name) { + case VoucherFilterKeys.saleType: + if (!active) { + return { + type: undefined + }; + } + + return { + type: value.map(type => findValueInEnum(type, VoucherDiscountType)) + }; + + case VoucherFilterKeys.started: + if (!active) { + return { + startedFrom: undefined, + startedTo: undefined + }; + } + if (multiple) { + return { + startedFrom: value[0], + startedTo: value[1] + }; + } + + return { + startedFrom: value[0], + startedTo: value[0] + }; + + case VoucherFilterKeys.timesUsed: + if (!active) { + return { + timesUsedFrom: undefined, + timesUsedTo: undefined + }; + } + if (multiple) { + return { + timesUsedFrom: value[0], + timesUsedTo: value[1] + }; + } + + return { + timesUsedFrom: value[0], + timesUsedTo: value[0] + }; + + case VoucherFilterKeys.status: + if (!active) { + return { + status: undefined + }; + } + return { + status: value.map(val => findValueInEnum(val, DiscountStatusEnum)) + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/discounts/views/VoucherList/messages.ts b/src/discounts/views/VoucherList/messages.ts new file mode 100644 index 000000000..df51e9142 --- /dev/null +++ b/src/discounts/views/VoucherList/messages.ts @@ -0,0 +1,41 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "voucher status" + }, + expired: { + defaultMessage: "Expired", + description: "voucher status" + }, + fixed: { + defaultMessage: "Fixed amount", + description: "discount type" + }, + percentage: { + defaultMessage: "Percentage", + description: "discount type" + }, + scheduled: { + defaultMessage: "Scheduled", + description: "voucher status" + }, + started: { + defaultMessage: "Started", + description: "voucher start date" + }, + status: { + defaultMessage: "Status", + description: "voucher status" + }, + timesUsed: { + defaultMessage: "Times used", + description: "voucher" + }, + type: { + defaultMessage: "Discount Type" + } +}); + +export default messages; diff --git a/src/storybook/stories/discounts/VoucherListPage.tsx b/src/storybook/stories/discounts/VoucherListPage.tsx index ffa4dff49..89b232735 100644 --- a/src/storybook/stories/discounts/VoucherListPage.tsx +++ b/src/storybook/stories/discounts/VoucherListPage.tsx @@ -2,6 +2,10 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { VoucherListUrlSortField } from "@saleor/discounts/urls"; +import { + VoucherDiscountType, + DiscountStatusEnum +} from "@saleor/types/globalTypes"; import VoucherListPage, { VoucherListPageProps } from "../../../discounts/components/VoucherListPage"; @@ -11,7 +15,8 @@ import { pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; @@ -21,7 +26,32 @@ const props: VoucherListPageProps = { ...searchPageProps, ...sortPageProps, ...tabPageProps, + ...filterPageProps, defaultCurrency: "USD", + filterOpts: { + saleType: { + active: false, + value: [VoucherDiscountType.FIXED, VoucherDiscountType.PERCENTAGE] + }, + started: { + active: false, + value: { + max: undefined, + min: undefined + } + }, + status: { + active: false, + value: [DiscountStatusEnum.ACTIVE] + }, + timesUsed: { + active: false, + value: { + max: undefined, + min: undefined + } + } + }, sort: { ...sortPageProps.sort, sort: VoucherListUrlSortField.code From c6783e569cfaee9df86fdeb7771011930fa6a6eb Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 3 Jan 2020 16:23:31 +0100 Subject: [PATCH 30/85] Update snapshots --- .../__snapshots__/Stories.test.ts.snap | 90 +++++++++++++++++-- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index d57cbab08..34046ae08 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -45016,8 +45016,21 @@ exports[`Storyshots Views / Discounts / Sale list default 1`] = `
+
+ +
@@ -45579,8 +45592,21 @@ exports[`Storyshots Views / Discounts / Sale list loading 1`] = `
+
+ +
@@ -45972,8 +45998,21 @@ exports[`Storyshots Views / Discounts / Sale list no data 1`] = `
+
+ +
@@ -51189,8 +51228,21 @@ exports[`Storyshots Views / Discounts / Voucher list default 1`] = `
+
+ +
@@ -51665,8 +51717,21 @@ exports[`Storyshots Views / Discounts / Voucher list loading 1`] = `
+
+ +
@@ -52104,8 +52169,21 @@ exports[`Storyshots Views / Discounts / Voucher list no data 1`] = `
+
+ +
From effcca27ac9ee07a1c6d16139b6987073a4c0d39 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 2 Jan 2020 17:55:24 +0100 Subject: [PATCH 31/85] Add text field support --- src/components/Filter/FilterContent.tsx | 23 +++++++++++++++++++++++ src/utils/filters/fields.ts | 15 +++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index 4e3f72886..f9cf27caa 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -148,6 +148,29 @@ const FilterContent: React.FC = ({
{filterField.active && (
+ {filterField.type === FieldType.text && ( + + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: [event.target.value, filterField.value[1]] + } + }, + type: "set-property" + }) + } + /> + )} {[FieldType.date, FieldType.price, FieldType.number].includes( filterField.type ) && ( diff --git a/src/utils/filters/fields.ts b/src/utils/filters/fields.ts index 3d5d4cd91..fb86a4a2f 100644 --- a/src/utils/filters/fields.ts +++ b/src/utils/filters/fields.ts @@ -64,3 +64,18 @@ export function createOptionsField( value: defaultValue }; } + +export function createTextField( + name: T, + label: string, + defaultValue: string +): IFilterElement { + return { + active: false, + label, + multiple: false, + name, + type: FieldType.text, + value: [defaultValue] + }; +} From 43a72d8436bd620b1910e0677ee9217e8eecafa4 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 2 Jan 2020 17:55:34 +0100 Subject: [PATCH 32/85] Add filters to draft order list --- .../OrderDraftListPage/OrderDraftListPage.tsx | 25 +++- src/orders/types.ts | 5 + src/orders/urls.ts | 3 + .../views/OrderDraftList/OrderDraftList.tsx | 42 ++++++- src/orders/views/OrderDraftList/filter.ts | 110 +++++++++++++++++- src/orders/views/OrderDraftList/messages.ts | 14 +++ 6 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 src/orders/views/OrderDraftList/messages.ts diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index c42bbc0a8..921fe553e 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -6,34 +6,42 @@ 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, - SearchPageProps, TabPageProps, - SortPage + SortPage, + FilterPageProps } from "@saleor/types"; import { OrderDraftListUrlSortField } from "@saleor/orders/urls"; -import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList"; +import { + OrderDraftFilterKeys, + createFilterStructure +} from "@saleor/orders/views/OrderDraftList/filter"; +import { OrderDraftListFilterOpts } from "@saleor/orders/types"; +import FilterBar from "@saleor/components/FilterBar"; import OrderDraftList from "../OrderDraftList"; +import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList"; export interface OrderDraftListPageProps extends PageListProps, ListActions, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { orders: OrderDraftList_draftOrders_edges_node[]; } const OrderDraftListPage: React.FC = ({ + currencySymbol, currentTab, disabled, + filterOpts, initialSearch, onAdd, onAll, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -43,6 +51,8 @@ const OrderDraftListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -59,18 +69,21 @@ const OrderDraftListPage: React.FC = ({ - ; status: FilterOpts; } + +export interface OrderDraftListFilterOpts { + created: FilterOpts; + customer: FilterOpts; +} diff --git a/src/orders/urls.ts b/src/orders/urls.ts index 145e62c19..2dbfb6dfb 100644 --- a/src/orders/urls.ts +++ b/src/orders/urls.ts @@ -55,6 +55,9 @@ export const orderListUrl = (params?: OrderListUrlQueryParams): string => { export const orderDraftListPath = urlJoin(orderSectionUrl, "drafts"); export enum OrderDraftListUrlFiltersEnum { + createdFrom = "createdFrom", + createdTo = "createdTo", + customer = "customer", query = "query" } export type OrderDraftListUrlFilters = Filters; diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 8c500c1c8..f360d0360 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -21,6 +21,9 @@ import { ListViews } from "@saleor/types"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { getSortParams } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { IFilter } from "@saleor/components/Filter"; +import useShop from "@saleor/hooks/useShop"; import OrderDraftListPage from "../../components/OrderDraftListPage"; import { TypedOrderDraftBulkCancelMutation, @@ -42,7 +45,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + getFilterQueryParam, + OrderDraftFilterKeys, + getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -61,6 +67,7 @@ export const OrderDraftList: React.FC = ({ params }) => { ListViews.DRAFT_LIST ); const intl = useIntl(); + const shop = useShop(); const handleCreateOrderCreateSuccess = (data: OrderDraftCreate) => { notify({ @@ -84,17 +91,38 @@ export const OrderDraftList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: OrderDraftListUrlFilters) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( orderDraftListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); }; + const resetFilters = () => { + reset(); + navigate( + orderDraftListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + + const handleSearchChange = (query: string) => { + reset(); + navigate( + orderDraftListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const [openModal, closeModal] = createDialogActionHandlers< OrderDraftListUrlDialog, OrderDraftListUrlQueryParams @@ -155,6 +183,7 @@ export const OrderDraftList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, orderDraftListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( @@ -169,9 +198,12 @@ export const OrderDraftList: React.FC = ({ params }) => { return ( <> changeFilterField({ query })} + onSearchChange={handleSearchChange} + onFilterChange={changeFilters} onAll={() => navigate(orderDraftListUrl())} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} diff --git a/src/orders/views/OrderDraftList/filter.ts b/src/orders/views/OrderDraftList/filter.ts index 76e72e29e..034952559 100644 --- a/src/orders/views/OrderDraftList/filter.ts +++ b/src/orders/views/OrderDraftList/filter.ts @@ -1,24 +1,126 @@ +import { IntlShape } from "react-intl"; + import { OrderDraftFilterInput } from "@saleor/types/globalTypes"; -import { - createFilterTabUtils, - createFilterUtils -} from "../../../utils/filters"; +import { maybe } from "@saleor/misc"; +import { OrderDraftListFilterOpts } from "@saleor/orders/types"; +import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { createTextField, createDateField } from "@saleor/utils/filters/fields"; import { OrderDraftListUrlFilters, OrderDraftListUrlFiltersEnum, OrderDraftListUrlQueryParams } from "../../urls"; +import { + createFilterTabUtils, + createFilterUtils +} from "../../../utils/filters"; +import messages from "./messages"; export const ORDER_DRAFT_FILTERS_KEY = "orderDraftFilters"; +export enum OrderDraftFilterKeys { + created = "created", + customer = "customer" +} + +export function getFilterOpts( + params: OrderDraftListUrlFilters +): OrderDraftListFilterOpts { + return { + created: { + active: maybe( + () => + [params.createdFrom, params.createdTo].some( + field => field !== undefined + ), + false + ), + value: { + max: maybe(() => params.createdTo), + min: maybe(() => params.createdFrom) + } + }, + customer: { + active: !!maybe(() => params.customer), + value: params.customer + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: OrderDraftListFilterOpts +): IFilter { + return [ + { + ...createDateField( + OrderDraftFilterKeys.created, + intl.formatMessage(messages.created), + opts.created.value + ), + active: opts.created.active + }, + { + ...createTextField( + OrderDraftFilterKeys.customer, + intl.formatMessage(messages.customer), + opts.customer.value + ), + active: opts.customer.active + } + ]; +} + export function getFilterVariables( params: OrderDraftListUrlFilters ): OrderDraftFilterInput { return { + created: { + gte: params.createdFrom, + lte: params.createdTo + }, + customer: params.customer, search: params.query }; } +export function getFilterQueryParam( + filter: IFilterElement +): OrderDraftListUrlFilters { + const { active, multiple, name, value } = filter; + + switch (name) { + case OrderDraftFilterKeys.created: + if (!active) { + return { + createdFrom: undefined, + createdTo: undefined + }; + } + if (multiple) { + return { + createdFrom: value[0], + createdTo: value[1] + }; + } + + return { + createdFrom: value[0], + createdTo: value[0] + }; + + case OrderDraftFilterKeys.customer: + if (!active) { + return { + customer: undefined + }; + } + return { + customer: value[0] + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/orders/views/OrderDraftList/messages.ts b/src/orders/views/OrderDraftList/messages.ts new file mode 100644 index 000000000..6403bbd02 --- /dev/null +++ b/src/orders/views/OrderDraftList/messages.ts @@ -0,0 +1,14 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + customer: { + defaultMessage: "Customer", + description: "draft order" + }, + created: { + defaultMessage: "Created", + description: "draft order" + } +}); + +export default messages; From 8b2aeaf58a723ecba0b67bf45dae5cc034a250ca Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 3 Jan 2020 10:16:37 +0100 Subject: [PATCH 33/85] Fix stories --- .../views/OrderDraftList/OrderDraftList.tsx | 3 +-- .../stories/orders/OrderDraftListPage.tsx | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index f360d0360..47b310998 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -34,7 +34,6 @@ import { OrderDraftBulkCancel } from "../../types/OrderDraftBulkCancel"; import { OrderDraftCreate } from "../../types/OrderDraftCreate"; import { orderDraftListUrl, - OrderDraftListUrlFilters, OrderDraftListUrlQueryParams, orderUrl, OrderDraftListUrlDialog @@ -204,7 +203,7 @@ export const OrderDraftList: React.FC = ({ params }) => { initialSearch={params.query || ""} onSearchChange={handleSearchChange} onFilterChange={changeFilters} - onAll={() => navigate(orderDraftListUrl())} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/storybook/stories/orders/OrderDraftListPage.tsx b/src/storybook/stories/orders/OrderDraftListPage.tsx index 429a40b36..85d8ffec3 100644 --- a/src/storybook/stories/orders/OrderDraftListPage.tsx +++ b/src/storybook/stories/orders/OrderDraftListPage.tsx @@ -7,7 +7,8 @@ import { pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "../../../fixtures"; import OrderDraftListPage, { OrderDraftListPageProps @@ -21,6 +22,20 @@ const props: OrderDraftListPageProps = { ...searchPageProps, ...sortPageProps, ...tabPageProps, + ...filterPageProps, + filterOpts: { + created: { + active: false, + value: { + max: undefined, + min: undefined + } + }, + customer: { + active: false, + value: undefined + } + }, onAdd: () => undefined, orders, sort: { From 310c9946c12045037c5f1c8e3383094bc33ab059 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 3 Jan 2020 10:18:35 +0100 Subject: [PATCH 34/85] Update snapshots --- .../__snapshots__/Stories.test.ts.snap | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 34046ae08..f9b7c283f 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -56675,8 +56675,21 @@ exports[`Storyshots Views / Orders / Draft order list default 1`] = `
+
+ +
@@ -57914,8 +57927,21 @@ exports[`Storyshots Views / Orders / Draft order list loading 1`] = `
+
+ +
@@ -58312,8 +58338,21 @@ exports[`Storyshots Views / Orders / Draft order list when no data 1`] = `
+
+ +
From a7304dabb0ef13014ca5e01b198ff950863b8dfe Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 9 Jan 2020 14:38:04 +0100 Subject: [PATCH 35/85] Create generic filtering handlers --- src/types.ts | 3 ++ src/utils/handlers/filterHandlers.ts | 65 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/utils/handlers/filterHandlers.ts diff --git a/src/types.ts b/src/types.ts index 977cd1bf2..dc2011c19 100644 --- a/src/types.ts +++ b/src/types.ts @@ -134,6 +134,9 @@ export type Filters = Partial< export type FiltersWithMultipleValues = Partial< Record >; +export type Search = Partial<{ + query: string; +}>; export type SingleAction = Partial<{ id: string; }>; diff --git a/src/utils/handlers/filterHandlers.ts b/src/utils/handlers/filterHandlers.ts new file mode 100644 index 000000000..3b932fd8b --- /dev/null +++ b/src/utils/handlers/filterHandlers.ts @@ -0,0 +1,65 @@ +import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; +import { Sort, Pagination, ActiveTab, Search } from "@saleor/types"; +import { getFilterQueryParams } from "../filters"; + +type RequiredParams = ActiveTab & Search & Sort & Pagination; +type CreateUrl = (params: RequiredParams) => string; +type GetFilterQueryParam< + TFilterKeys extends string, + TFilters extends object +> = (filter: IFilterElement) => TFilters; +type CreateFilterHandlers = [ + (filter: IFilter) => void, + () => void, + (query: string) => void +]; + +function createFilterHandlers< + TFilterKeys extends string, + TFilters extends object +>(opts: { + getFilterQueryParam: GetFilterQueryParam; + navigate: UseNavigatorResult; + createUrl: CreateUrl; + params: RequiredParams; + cleanupFn: () => void; +}): CreateFilterHandlers { + const { getFilterQueryParam, navigate, createUrl, params, cleanupFn } = opts; + + const changeFilters = (filter: IFilter) => { + cleanupFn(); + navigate( + createUrl({ + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), + activeTab: undefined + }) + ); + }; + + const resetFilters = () => { + cleanupFn(); + navigate( + createUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + + const handleSearchChange = (query: string) => { + cleanupFn(); + navigate( + createUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + + return [changeFilters, resetFilters, handleSearchChange]; +} + +export default createFilterHandlers; From be649e32ea409477e59c0937aca5fbece639ced8 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 9 Jan 2020 14:45:22 +0100 Subject: [PATCH 36/85] Use generic handlers --- .../views/CollectionList/CollectionList.tsx | 46 +++++-------------- .../views/CustomerList/CustomerList.tsx | 46 +++++-------------- src/discounts/views/SaleList/SaleList.tsx | 46 +++++-------------- .../views/VoucherList/VoucherList.tsx | 43 +++++------------ .../views/OrderDraftList/OrderDraftList.tsx | 46 +++++-------------- src/orders/views/OrderList/OrderList.tsx | 46 +++++-------------- .../views/ProductTypeList/ProductTypeList.tsx | 46 +++++-------------- .../views/ProductList/ProductList.tsx | 46 +++++-------------- src/staff/views/StaffList/StaffList.tsx | 39 +++++----------- src/utils/handlers/filterHandlers.ts | 17 +++++-- 10 files changed, 120 insertions(+), 301 deletions(-) diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 0523c8f77..20a39a6bf 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -23,9 +23,8 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { IFilter } from "@saleor/components/Filter"; -import { getFilterQueryParams } from "@saleor/utils/filters"; import useShop from "@saleor/hooks/useShop"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import CollectionListPage from "../../components/CollectionListPage/CollectionListPage"; import { TypedCollectionBulkDelete, @@ -48,7 +47,6 @@ import { getFilterTabs, getFilterVariables, saveFilterTab, - CollectionFilterKeys, getFilterQueryParam, getFilterOpts } from "./filter"; @@ -94,37 +92,17 @@ export const CollectionList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filter: IFilter) => { - reset(); - navigate( - collectionListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - }; - - const resetFilters = () => { - reset(); - navigate( - collectionListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - }; - - const handleSearchChange = (query: string) => { - reset(); - navigate( - collectionListUrl({ - ...params, - activeTab: undefined, - query - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: collectionListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< CollectionListUrlDialog, diff --git a/src/customers/views/CustomerList/CustomerList.tsx b/src/customers/views/CustomerList/CustomerList.tsx index a2641a553..9f61f4f36 100644 --- a/src/customers/views/CustomerList/CustomerList.tsx +++ b/src/customers/views/CustomerList/CustomerList.tsx @@ -22,9 +22,8 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { IFilter } from "@saleor/components/Filter"; -import { getFilterQueryParams } from "@saleor/utils/filters"; import useShop from "@saleor/hooks/useShop"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import CustomerListPage from "../../components/CustomerListPage"; import { TypedBulkRemoveCustomers } from "../../mutations"; import { useCustomerListQuery } from "../../queries"; @@ -43,7 +42,6 @@ import { getFilterTabs, getFilterVariables, saveFilterTab, - CustomerFilterKeys, getFilterQueryParam, getFilterOpts } from "./filter"; @@ -89,37 +87,17 @@ export const CustomerList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filter: IFilter) => { - reset(); - navigate( - customerListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - }; - - const resetFilters = () => { - reset(); - navigate( - customerListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - }; - - const handleSearchChange = (query: string) => { - reset(); - navigate( - customerListUrl({ - ...params, - activeTab: undefined, - query - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: customerListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< CustomerListUrlDialog, diff --git a/src/discounts/views/SaleList/SaleList.tsx b/src/discounts/views/SaleList/SaleList.tsx index 16d6d33b8..e014d9124 100644 --- a/src/discounts/views/SaleList/SaleList.tsx +++ b/src/discounts/views/SaleList/SaleList.tsx @@ -24,8 +24,7 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { IFilter } from "@saleor/components/Filter"; -import { getFilterQueryParams } from "@saleor/utils/filters"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import SaleListPage from "../../components/SaleListPage"; import { TypedSaleBulkDelete } from "../../mutations"; import { useSaleListQuery } from "../../queries"; @@ -44,7 +43,6 @@ import { getFilterTabs, getFilterVariables, saveFilterTab, - SaleFilterKeys, getFilterQueryParam, getFilterOpts } from "./filter"; @@ -90,37 +88,17 @@ export const SaleList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filter: IFilter) => { - reset(); - navigate( - saleListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - }; - - const resetFilters = () => { - reset(); - navigate( - saleListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - }; - - const handleSearchChange = (query: string) => { - reset(); - navigate( - saleListUrl({ - ...params, - activeTab: undefined, - query - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: saleListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< SaleListUrlDialog, diff --git a/src/discounts/views/VoucherList/VoucherList.tsx b/src/discounts/views/VoucherList/VoucherList.tsx index f9931f941..bf2ad956b 100644 --- a/src/discounts/views/VoucherList/VoucherList.tsx +++ b/src/discounts/views/VoucherList/VoucherList.tsx @@ -26,6 +26,7 @@ import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import { getFilterQueryParams } from "@saleor/utils/filters"; import { IFilter } from "@saleor/components/Filter"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import VoucherListPage from "../../components/VoucherListPage"; import { TypedVoucherBulkDelete } from "../../mutations"; import { useVoucherListQuery } from "../../queries"; @@ -90,37 +91,17 @@ export const VoucherList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filter: IFilter) => { - reset(); - navigate( - voucherListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - }; - - const resetFilters = () => { - reset(); - navigate( - voucherListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - }; - - const handleSearchChange = (query: string) => { - reset(); - navigate( - voucherListUrl({ - ...params, - activeTab: undefined, - query - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: voucherListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< VoucherListUrlDialog, diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 47b310998..b4965c480 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -21,9 +21,8 @@ import { ListViews } from "@saleor/types"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { getSortParams } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { getFilterQueryParams } from "@saleor/utils/filters"; -import { IFilter } from "@saleor/components/Filter"; import useShop from "@saleor/hooks/useShop"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import OrderDraftListPage from "../../components/OrderDraftListPage"; import { TypedOrderDraftBulkCancelMutation, @@ -46,7 +45,6 @@ import { getFilterVariables, saveFilterTab, getFilterQueryParam, - OrderDraftFilterKeys, getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -90,37 +88,17 @@ export const OrderDraftList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filter: IFilter) => { - reset(); - navigate( - orderDraftListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - }; - - const resetFilters = () => { - reset(); - navigate( - orderDraftListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - }; - - const handleSearchChange = (query: string) => { - reset(); - navigate( - orderDraftListUrl({ - ...params, - activeTab: undefined, - query - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: orderDraftListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< OrderDraftListUrlDialog, diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 38058b842..f078616e9 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -19,8 +19,7 @@ import { ListViews } from "@saleor/types"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { getSortParams } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { IFilter } from "@saleor/components/Filter"; -import { getFilterQueryParams } from "@saleor/utils/filters"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import OrderBulkCancelDialog from "../../components/OrderBulkCancelDialog"; import OrderListPage from "../../components/OrderListPage/OrderListPage"; import { @@ -44,7 +43,6 @@ import { getFilterOpts, getFilterVariables, saveFilterTab, - OrderFilterKeys, getFilterQueryParam } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -88,37 +86,17 @@ export const OrderList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filter: IFilter) => { - reset(); - navigate( - orderListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - }; - - const resetFilters = () => { - reset(); - navigate( - orderListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - }; - - const handleSearchChange = (query: string) => { - reset(); - navigate( - orderListUrl({ - ...params, - activeTab: undefined, - query - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: orderListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< OrderListUrlDialog, diff --git a/src/productTypes/views/ProductTypeList/ProductTypeList.tsx b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx index bd901703b..0e088818b 100644 --- a/src/productTypes/views/ProductTypeList/ProductTypeList.tsx +++ b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx @@ -21,9 +21,8 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { IFilter } from "@saleor/components/Filter"; -import { getFilterQueryParams } from "@saleor/utils/filters"; import useShop from "@saleor/hooks/useShop"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import { configurationMenuUrl } from "../../../configuration"; import { maybe } from "../../../misc"; import ProductTypeListPage from "../../components/ProductTypeListPage"; @@ -44,7 +43,6 @@ import { getFilterTabs, getFilterVariables, saveFilterTab, - ProductTypeFilterKeys, getFilterQueryParam, getFilterOpts } from "./filter"; @@ -88,37 +86,17 @@ export const ProductTypeList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filter: IFilter) => { - reset(); - navigate( - productTypeListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - }; - - const resetFilters = () => { - reset(); - navigate( - productTypeListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - }; - - const handleSearchChange = (query: string) => { - reset(); - navigate( - productTypeListUrl({ - ...params, - activeTab: undefined, - query - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: productTypeListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< ProductTypeListUrlDialog, diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index e9386c362..7eb034d10 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -25,8 +25,7 @@ import { ProductListVariables } from "@saleor/products/types/ProductList"; import { ListViews } from "@saleor/types"; import { getSortUrlVariables } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { IFilter } from "@saleor/components/Filter"; -import { getFilterQueryParams } from "@saleor/utils/filters"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import ProductListPage from "../../components/ProductListPage"; import { TypedProductBulkDeleteMutation, @@ -53,7 +52,6 @@ import { getFilterTabs, getFilterVariables, saveFilterTab, - ProductFilterKeys, getFilterOpts, getFilterQueryParam } from "./filters"; @@ -103,37 +101,17 @@ export const ProductList: React.FC = ({ params }) => { ProductListUrlQueryParams >(navigate, productListUrl, params); - const changeFilters = (filter: IFilter) => { - reset(); - navigate( - productListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - }; - - const resetFilters = () => { - reset(); - navigate( - productListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - }; - - const handleSearchChange = (query: string) => { - reset(); - navigate( - productListUrl({ - ...params, - activeTab: undefined, - query - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: productListUrl, + getFilterQueryParam, + navigate, + params + }); const handleTabChange = (tab: number) => { reset(); diff --git a/src/staff/views/StaffList/StaffList.tsx b/src/staff/views/StaffList/StaffList.tsx index 9dfa5be9c..8967a283c 100644 --- a/src/staff/views/StaffList/StaffList.tsx +++ b/src/staff/views/StaffList/StaffList.tsx @@ -23,8 +23,7 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { IFilter } from "@saleor/components/Filter"; -import { getFilterQueryParams } from "@saleor/utils/filters"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import StaffAddMemberDialog, { FormData as AddStaffMemberForm } from "../../components/StaffAddMemberDialog"; @@ -45,7 +44,6 @@ import { getFilterTabs, getFilterVariables, saveFilterTab, - StaffFilterKeys, getFilterQueryParam, getFilterOpts } from "./filter"; @@ -89,31 +87,16 @@ export const StaffList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilters = (filter: IFilter) => - navigate( - staffListUrl({ - ...params, - ...getFilterQueryParams(filter, getFilterQueryParam), - activeTab: undefined - }) - ); - - const resetFilters = () => - navigate( - staffListUrl({ - asc: params.asc, - sort: params.sort - }) - ); - - const handleSearchChange = (query: string) => - navigate( - staffListUrl({ - ...params, - activeTab: undefined, - query - }) - ); + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + createUrl: staffListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< StaffListUrlDialog, diff --git a/src/utils/handlers/filterHandlers.ts b/src/utils/handlers/filterHandlers.ts index 3b932fd8b..3c70db069 100644 --- a/src/utils/handlers/filterHandlers.ts +++ b/src/utils/handlers/filterHandlers.ts @@ -23,12 +23,15 @@ function createFilterHandlers< navigate: UseNavigatorResult; createUrl: CreateUrl; params: RequiredParams; - cleanupFn: () => void; + cleanupFn?: () => void; }): CreateFilterHandlers { const { getFilterQueryParam, navigate, createUrl, params, cleanupFn } = opts; const changeFilters = (filter: IFilter) => { - cleanupFn(); + if (!!cleanupFn) { + cleanupFn(); + } + navigate( createUrl({ ...params, @@ -39,7 +42,10 @@ function createFilterHandlers< }; const resetFilters = () => { - cleanupFn(); + if (!!cleanupFn) { + cleanupFn(); + } + navigate( createUrl({ asc: params.asc, @@ -49,7 +55,10 @@ function createFilterHandlers< }; const handleSearchChange = (query: string) => { - cleanupFn(); + if (!!cleanupFn) { + cleanupFn(); + } + navigate( createUrl({ ...params, From 595f628245872d6784af3689a7100870efd3a27d Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 9 Jan 2020 15:41:15 +0100 Subject: [PATCH 37/85] Remove unused imports --- src/discounts/views/VoucherList/VoucherList.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/discounts/views/VoucherList/VoucherList.tsx b/src/discounts/views/VoucherList/VoucherList.tsx index bf2ad956b..e4b41841f 100644 --- a/src/discounts/views/VoucherList/VoucherList.tsx +++ b/src/discounts/views/VoucherList/VoucherList.tsx @@ -24,8 +24,6 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { getFilterQueryParams } from "@saleor/utils/filters"; -import { IFilter } from "@saleor/components/Filter"; import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import VoucherListPage from "../../components/VoucherListPage"; import { TypedVoucherBulkDelete } from "../../mutations"; @@ -46,7 +44,6 @@ import { getFilterVariables, saveFilterTab, getFilterQueryParam, - VoucherFilterKeys, getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; From 02a24de1033aeb878750a7b36aa3ec0d3fb9e238 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 13:45:56 +0100 Subject: [PATCH 38/85] Separate visual and app layer in collections --- .../CollectionListPage/CollectionListPage.tsx | 10 ++-- .../components/CollectionListPage/filters.ts | 53 +++++++++++++++++++ .../components/CollectionListPage/index.ts | 1 + src/collections/types.ts | 6 --- .../views/CollectionList/CollectionList.tsx | 2 +- .../CollectionList/{filter.ts => filters.ts} | 43 ++------------- .../views/CollectionList/messages.ts | 14 ----- 7 files changed, 65 insertions(+), 64 deletions(-) create mode 100644 src/collections/components/CollectionListPage/filters.ts delete mode 100644 src/collections/types.ts rename src/collections/views/CollectionList/{filter.ts => filters.ts} (61%) delete mode 100644 src/collections/views/CollectionList/messages.ts diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index cf20510bf..dcf23c717 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -15,13 +15,13 @@ import { SortPage } from "@saleor/types"; import { CollectionListUrlSortField } from "@saleor/collections/urls"; -import { - CollectionFilterKeys, - createFilterStructure -} from "@saleor/collections/views/CollectionList/filter"; -import { CollectionListFilterOpts } from "@saleor/collections/types"; import { CollectionList_collections_edges_node } from "../../types/CollectionList"; import CollectionList from "../CollectionList/CollectionList"; +import { + CollectionFilterKeys, + CollectionListFilterOpts, + createFilterStructure +} from "./filters"; export interface CollectionListPageProps extends PageListProps, diff --git a/src/collections/components/CollectionListPage/filters.ts b/src/collections/components/CollectionListPage/filters.ts new file mode 100644 index 000000000..fdcd1478e --- /dev/null +++ b/src/collections/components/CollectionListPage/filters.ts @@ -0,0 +1,53 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { commonMessages } from "@saleor/intl"; +import { FilterOpts } from "@saleor/types"; +import { CollectionPublished } from "@saleor/types/globalTypes"; +import { IFilter } from "@saleor/components/Filter"; +import { createOptionsField } from "@saleor/utils/filters/fields"; + +export interface CollectionListFilterOpts { + status: FilterOpts; +} + +export enum CollectionFilterKeys { + status = "status" +} + +const messages = defineMessages({ + hidden: { + defaultMessage: "Hidden", + description: "collection" + }, + published: { + defaultMessage: "Published", + description: "collection" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: CollectionListFilterOpts +): IFilter { + return [ + { + ...createOptionsField( + CollectionFilterKeys.status, + intl.formatMessage(commonMessages.status), + [opts.status.value], + false, + [ + { + label: intl.formatMessage(messages.published), + value: CollectionPublished.PUBLISHED + }, + { + label: intl.formatMessage(messages.hidden), + value: CollectionPublished.HIDDEN + } + ] + ), + active: opts.status.active + } + ]; +} diff --git a/src/collections/components/CollectionListPage/index.ts b/src/collections/components/CollectionListPage/index.ts index 3324e1926..353abb74d 100644 --- a/src/collections/components/CollectionListPage/index.ts +++ b/src/collections/components/CollectionListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./CollectionListPage"; export * from "./CollectionListPage"; +export * from "./filters"; diff --git a/src/collections/types.ts b/src/collections/types.ts deleted file mode 100644 index 1adfc9d03..000000000 --- a/src/collections/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FilterOpts } from "@saleor/types"; -import { CollectionPublished } from "@saleor/types/globalTypes"; - -export interface CollectionListFilterOpts { - status: FilterOpts; -} diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 20a39a6bf..dbc0c3be2 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -49,7 +49,7 @@ import { saveFilterTab, getFilterQueryParam, getFilterOpts -} from "./filter"; +} from "./filters"; import { getSortQueryVariables } from "./sort"; interface CollectionListProps { diff --git a/src/collections/views/CollectionList/filter.ts b/src/collections/views/CollectionList/filters.ts similarity index 61% rename from src/collections/views/CollectionList/filter.ts rename to src/collections/views/CollectionList/filters.ts index 1e09bfed7..bbc066e26 100644 --- a/src/collections/views/CollectionList/filter.ts +++ b/src/collections/views/CollectionList/filters.ts @@ -1,14 +1,13 @@ -import { IntlShape } from "react-intl"; - import { CollectionFilterInput, CollectionPublished } from "@saleor/types/globalTypes"; -import { IFilterElement, IFilter } from "@saleor/components/Filter"; +import { IFilterElement } from "@saleor/components/Filter"; import { maybe, findValueInEnum } from "@saleor/misc"; -import { createOptionsField } from "@saleor/utils/filters/fields"; -import { commonMessages } from "@saleor/intl"; -import { CollectionListFilterOpts } from "../../types"; +import { + CollectionListFilterOpts, + CollectionFilterKeys +} from "@saleor/collections/components/CollectionListPage"; import { CollectionListUrlFilters, CollectionListUrlFiltersEnum, @@ -18,14 +17,9 @@ import { createFilterTabUtils, createFilterUtils } from "../../../utils/filters"; -import messages from "./messages"; export const COLLECTION_FILTERS_KEY = "collectionFilters"; -export enum CollectionFilterKeys { - status = "status" -} - export function getFilterOpts( params: CollectionListUrlFilters ): CollectionListFilterOpts { @@ -37,33 +31,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: CollectionListFilterOpts -): IFilter { - return [ - { - ...createOptionsField( - CollectionFilterKeys.status, - intl.formatMessage(commonMessages.status), - [opts.status.value], - false, - [ - { - label: intl.formatMessage(messages.published), - value: CollectionPublished.PUBLISHED - }, - { - label: intl.formatMessage(messages.hidden), - value: CollectionPublished.HIDDEN - } - ] - ), - active: opts.status.active - } - ]; -} - export function getFilterVariables( params: CollectionListUrlFilters ): CollectionFilterInput { diff --git a/src/collections/views/CollectionList/messages.ts b/src/collections/views/CollectionList/messages.ts deleted file mode 100644 index 3e3f6a327..000000000 --- a/src/collections/views/CollectionList/messages.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - hidden: { - defaultMessage: "Hidden", - description: "collection" - }, - published: { - defaultMessage: "Published", - description: "collection" - } -}); - -export default messages; From 4df5a99a689a3f881928397bf845e286e50e3680 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 13:50:28 +0100 Subject: [PATCH 39/85] Separate visual and app layer in customers --- .../CustomerListPage/CustomerListPage.tsx | 10 +-- .../components/CustomerListPage/filters.ts | 66 +++++++++++++++++++ .../components/CustomerListPage/index.ts | 1 + src/customers/types.ts | 8 --- .../views/CustomerList/CustomerList.tsx | 2 +- .../CustomerList/{filter.ts => filters.ts} | 50 ++------------ src/customers/views/CustomerList/messages.ts | 17 ----- 7 files changed, 77 insertions(+), 77 deletions(-) create mode 100644 src/customers/components/CustomerListPage/filters.ts rename src/customers/views/CustomerList/{filter.ts => filters.ts} (75%) delete mode 100644 src/customers/views/CustomerList/messages.ts diff --git a/src/customers/components/CustomerListPage/CustomerListPage.tsx b/src/customers/components/CustomerListPage/CustomerListPage.tsx index 371e2ec98..2c45b6f05 100644 --- a/src/customers/components/CustomerListPage/CustomerListPage.tsx +++ b/src/customers/components/CustomerListPage/CustomerListPage.tsx @@ -14,14 +14,14 @@ import { FilterPageProps } from "@saleor/types"; import { CustomerListUrlSortField } from "@saleor/customers/urls"; -import { - CustomerFilterKeys, - createFilterStructure -} from "@saleor/customers/views/CustomerList/filter"; -import { CustomerListFilterOpts } from "@saleor/customers/types"; import FilterBar from "@saleor/components/FilterBar"; import CustomerList from "../CustomerList/CustomerList"; import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; +import { + CustomerFilterKeys, + CustomerListFilterOpts, + createFilterStructure +} from "./filters"; export interface CustomerListPageProps extends PageListProps, diff --git a/src/customers/components/CustomerListPage/filters.ts b/src/customers/components/CustomerListPage/filters.ts new file mode 100644 index 000000000..71301bac9 --- /dev/null +++ b/src/customers/components/CustomerListPage/filters.ts @@ -0,0 +1,66 @@ +import { IntlShape, defineMessages } from "react-intl"; + +import { FilterOpts, MinMax } from "@saleor/types"; +import { IFilter } from "@saleor/components/Filter"; +import { + createDateField, + createNumberField +} from "@saleor/utils/filters/fields"; + +export enum CustomerFilterKeys { + joined = "joined", + moneySpent = "spent", + numberOfOrders = "orders" +} + +export interface CustomerListFilterOpts { + joined: FilterOpts; + moneySpent: FilterOpts; + numberOfOrders: FilterOpts; +} + +const messages = defineMessages({ + joinDate: { + defaultMessage: "Join Date", + description: "customer" + }, + moneySpent: { + defaultMessage: "Money Spent", + description: "customer" + }, + numberOfOrders: { + defaultMessage: "Number of Orders" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: CustomerListFilterOpts +): IFilter { + return [ + { + ...createDateField( + CustomerFilterKeys.joined, + intl.formatMessage(messages.joinDate), + opts.joined.value + ), + active: opts.joined.active + }, + { + ...createNumberField( + CustomerFilterKeys.moneySpent, + intl.formatMessage(messages.moneySpent), + opts.moneySpent.value + ), + active: opts.moneySpent.active + }, + { + ...createNumberField( + CustomerFilterKeys.numberOfOrders, + intl.formatMessage(messages.numberOfOrders), + opts.numberOfOrders.value + ), + active: opts.numberOfOrders.active + } + ]; +} diff --git a/src/customers/components/CustomerListPage/index.ts b/src/customers/components/CustomerListPage/index.ts index 59481bef5..fe790fd4f 100644 --- a/src/customers/components/CustomerListPage/index.ts +++ b/src/customers/components/CustomerListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./CustomerListPage"; export * from "./CustomerListPage"; +export * from "./filters"; diff --git a/src/customers/types.ts b/src/customers/types.ts index dfe785f5e..a0398abbb 100644 --- a/src/customers/types.ts +++ b/src/customers/types.ts @@ -1,11 +1,3 @@ -import { FilterOpts, MinMax } from "@saleor/types"; - -export interface CustomerListFilterOpts { - joined: FilterOpts; - moneySpent: FilterOpts; - numberOfOrders: FilterOpts; -} - export interface AddressTypeInput { city: string; cityArea?: string; diff --git a/src/customers/views/CustomerList/CustomerList.tsx b/src/customers/views/CustomerList/CustomerList.tsx index 9f61f4f36..7a5161949 100644 --- a/src/customers/views/CustomerList/CustomerList.tsx +++ b/src/customers/views/CustomerList/CustomerList.tsx @@ -44,7 +44,7 @@ import { saveFilterTab, getFilterQueryParam, getFilterOpts -} from "./filter"; +} from "./filters"; import { getSortQueryVariables } from "./sort"; interface CustomerListProps { diff --git a/src/customers/views/CustomerList/filter.ts b/src/customers/views/CustomerList/filters.ts similarity index 75% rename from src/customers/views/CustomerList/filter.ts rename to src/customers/views/CustomerList/filters.ts index 44c238aa8..92c398ac7 100644 --- a/src/customers/views/CustomerList/filter.ts +++ b/src/customers/views/CustomerList/filters.ts @@ -1,12 +1,10 @@ -import { IntlShape } from "react-intl"; - import { CustomerFilterInput } from "@saleor/types/globalTypes"; import { maybe } from "@saleor/misc"; +import { IFilterElement } from "@saleor/components/Filter"; import { - createDateField, - createNumberField -} from "@saleor/utils/filters/fields"; -import { IFilter, IFilterElement } from "@saleor/components/Filter"; + CustomerFilterKeys, + CustomerListFilterOpts +} from "@saleor/customers/components/CustomerListPage"; import { createFilterTabUtils, createFilterUtils @@ -16,17 +14,9 @@ import { CustomerListUrlFiltersEnum, CustomerListUrlQueryParams } from "../../urls"; -import { CustomerListFilterOpts } from "../../types"; -import messages from "./messages"; export const CUSTOMER_FILTERS_KEY = "customerFilters"; -export enum CustomerFilterKeys { - joined = "joined", - moneySpent = "spent", - numberOfOrders = "orders" -} - export function getFilterOpts( params: CustomerListUrlFilters ): CustomerListFilterOpts { @@ -73,38 +63,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: CustomerListFilterOpts -): IFilter { - return [ - { - ...createDateField( - CustomerFilterKeys.joined, - intl.formatMessage(messages.joinDate), - opts.joined.value - ), - active: opts.joined.active - }, - { - ...createNumberField( - CustomerFilterKeys.moneySpent, - intl.formatMessage(messages.moneySpent), - opts.moneySpent.value - ), - active: opts.moneySpent.active - }, - { - ...createNumberField( - CustomerFilterKeys.numberOfOrders, - intl.formatMessage(messages.numberOfOrders), - opts.numberOfOrders.value - ), - active: opts.numberOfOrders.active - } - ]; -} - export function getFilterVariables( params: CustomerListUrlFilters ): CustomerFilterInput { diff --git a/src/customers/views/CustomerList/messages.ts b/src/customers/views/CustomerList/messages.ts deleted file mode 100644 index 8ba6fd197..000000000 --- a/src/customers/views/CustomerList/messages.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - joinDate: { - defaultMessage: "Join Date", - description: "customer" - }, - moneySpent: { - defaultMessage: "Money Spent", - description: "customer" - }, - numberOfOrders: { - defaultMessage: "Number of Orders" - } -}); - -export default messages; From fc9a51c92a64d3d33325323e8df34a158bab340c Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 13:56:47 +0100 Subject: [PATCH 40/85] Separate visual and app layer in sales --- .../components/SaleListPage/SaleListPage.tsx | 10 +- .../components/SaleListPage/filters.ts | 116 ++++++++++++++++++ .../components/SaleListPage/index.ts | 1 + src/discounts/types.ts | 13 +- src/discounts/views/SaleList/SaleList.tsx | 2 +- .../views/SaleList/{filter.ts => filters.ts} | 80 +----------- src/discounts/views/SaleList/messages.ts | 37 ------ 7 files changed, 132 insertions(+), 127 deletions(-) create mode 100644 src/discounts/components/SaleListPage/filters.ts rename src/discounts/views/SaleList/{filter.ts => filters.ts} (61%) delete mode 100644 src/discounts/views/SaleList/messages.ts diff --git a/src/discounts/components/SaleListPage/SaleListPage.tsx b/src/discounts/components/SaleListPage/SaleListPage.tsx index e539cfbdf..b6c5ef9bd 100644 --- a/src/discounts/components/SaleListPage/SaleListPage.tsx +++ b/src/discounts/components/SaleListPage/SaleListPage.tsx @@ -15,13 +15,13 @@ import { FilterPageProps } from "@saleor/types"; import { SaleListUrlSortField } from "@saleor/discounts/urls"; -import { - SaleFilterKeys, - createFilterStructure -} from "@saleor/discounts/views/SaleList/filter"; -import { SaleListFilterOpts } from "@saleor/discounts/types"; import { SaleList_sales_edges_node } from "../../types/SaleList"; import SaleList from "../SaleList"; +import { + SaleFilterKeys, + SaleListFilterOpts, + createFilterStructure +} from "./filters"; export interface SaleListPageProps extends PageListProps, diff --git a/src/discounts/components/SaleListPage/filters.ts b/src/discounts/components/SaleListPage/filters.ts new file mode 100644 index 000000000..cd60e2cda --- /dev/null +++ b/src/discounts/components/SaleListPage/filters.ts @@ -0,0 +1,116 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { FilterOpts, MinMax } from "@saleor/types"; +import { + DiscountStatusEnum, + DiscountValueTypeEnum +} from "@saleor/types/globalTypes"; +import { + createDateField, + createOptionsField +} from "@saleor/utils/filters/fields"; +import { IFilter } from "@saleor/components/Filter"; + +export enum SaleFilterKeys { + saleType = "saleType", + started = "started", + status = "status" +} + +export interface SaleListFilterOpts { + saleType: FilterOpts; + started: FilterOpts; + status: FilterOpts; +} + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "sale status" + }, + expired: { + defaultMessage: "Expired", + description: "sale status" + }, + fixed: { + defaultMessage: "Fixed amount", + description: "discount type" + }, + percentage: { + defaultMessage: "Percentage", + description: "discount type" + }, + scheduled: { + defaultMessage: "Scheduled", + description: "sale status" + }, + started: { + defaultMessage: "Started", + description: "sale start date" + }, + status: { + defaultMessage: "Status", + description: "sale status" + }, + type: { + defaultMessage: "Discount Type" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: SaleListFilterOpts +): IFilter { + return [ + { + ...createDateField( + SaleFilterKeys.started, + intl.formatMessage(messages.started), + opts.started.value + ), + active: opts.started.active + }, + { + ...createOptionsField( + SaleFilterKeys.status, + intl.formatMessage(messages.status), + opts.status.value, + true, + [ + { + label: intl.formatMessage(messages.active), + value: DiscountStatusEnum.ACTIVE + }, + { + label: intl.formatMessage(messages.expired), + value: DiscountStatusEnum.EXPIRED + }, + { + label: intl.formatMessage(messages.scheduled), + value: DiscountStatusEnum.SCHEDULED + } + ] + ), + active: opts.status.active + }, + { + ...createOptionsField( + SaleFilterKeys.saleType, + intl.formatMessage(messages.type), + [opts.saleType.value], + false, + [ + { + label: intl.formatMessage(messages.fixed), + value: DiscountValueTypeEnum.FIXED + }, + { + label: intl.formatMessage(messages.percentage), + value: DiscountValueTypeEnum.PERCENTAGE + } + ] + ), + active: opts.saleType.active + } + ]; +} diff --git a/src/discounts/components/SaleListPage/index.ts b/src/discounts/components/SaleListPage/index.ts index 59dd2585a..adb62f183 100644 --- a/src/discounts/components/SaleListPage/index.ts +++ b/src/discounts/components/SaleListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./SaleListPage"; export * from "./SaleListPage"; +export * from "./filters"; diff --git a/src/discounts/types.ts b/src/discounts/types.ts index 20e9c1253..b51bc7313 100644 --- a/src/discounts/types.ts +++ b/src/discounts/types.ts @@ -1,15 +1,8 @@ -import { FilterOpts, MinMax } from "@saleor/types"; import { - DiscountStatusEnum, - DiscountValueTypeEnum, - VoucherDiscountType + VoucherDiscountType, + DiscountStatusEnum } from "@saleor/types/globalTypes"; - -export interface SaleListFilterOpts { - saleType: FilterOpts; - started: FilterOpts; - status: FilterOpts; -} +import { MinMax, FilterOpts } from "@saleor/types"; export interface VoucherListFilterOpts { saleType: FilterOpts; diff --git a/src/discounts/views/SaleList/SaleList.tsx b/src/discounts/views/SaleList/SaleList.tsx index e014d9124..e17631d28 100644 --- a/src/discounts/views/SaleList/SaleList.tsx +++ b/src/discounts/views/SaleList/SaleList.tsx @@ -45,7 +45,7 @@ import { saveFilterTab, getFilterQueryParam, getFilterOpts -} from "./filter"; +} from "./filters"; import { getSortQueryVariables } from "./sort"; interface SaleListProps { diff --git a/src/discounts/views/SaleList/filter.ts b/src/discounts/views/SaleList/filters.ts similarity index 61% rename from src/discounts/views/SaleList/filter.ts rename to src/discounts/views/SaleList/filters.ts index c7bae066b..3d70f2412 100644 --- a/src/discounts/views/SaleList/filter.ts +++ b/src/discounts/views/SaleList/filters.ts @@ -1,17 +1,14 @@ -import { IntlShape } from "react-intl"; - import { - SaleFilterInput, DiscountStatusEnum, - DiscountValueTypeEnum + DiscountValueTypeEnum, + SaleFilterInput } from "@saleor/types/globalTypes"; import { maybe, findValueInEnum, joinDateTime } from "@saleor/misc"; -import { SaleListFilterOpts } from "@saleor/discounts/types"; -import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { IFilterElement } from "@saleor/components/Filter"; import { - createDateField, - createOptionsField -} from "@saleor/utils/filters/fields"; + SaleListFilterOpts, + SaleFilterKeys +} from "@saleor/discounts/components/SaleListPage"; import { createFilterTabUtils, createFilterUtils, @@ -22,16 +19,9 @@ import { SaleListUrlFiltersEnum, SaleListUrlQueryParams } from "../../urls"; -import messages from "./messages"; export const SALE_FILTERS_KEY = "saleFilters"; -export enum SaleFilterKeys { - saleType = "saleType", - started = "started", - status = "status" -} - export function getFilterOpts(params: SaleListUrlFilters): SaleListFilterOpts { return { saleType: { @@ -66,64 +56,6 @@ export function getFilterOpts(params: SaleListUrlFilters): SaleListFilterOpts { }; } -export function createFilterStructure( - intl: IntlShape, - opts: SaleListFilterOpts -): IFilter { - return [ - { - ...createDateField( - SaleFilterKeys.started, - intl.formatMessage(messages.started), - opts.started.value - ), - active: opts.started.active - }, - { - ...createOptionsField( - SaleFilterKeys.status, - intl.formatMessage(messages.status), - opts.status.value, - true, - [ - { - label: intl.formatMessage(messages.active), - value: DiscountStatusEnum.ACTIVE - }, - { - label: intl.formatMessage(messages.expired), - value: DiscountStatusEnum.EXPIRED - }, - { - label: intl.formatMessage(messages.scheduled), - value: DiscountStatusEnum.SCHEDULED - } - ] - ), - active: opts.status.active - }, - { - ...createOptionsField( - SaleFilterKeys.saleType, - intl.formatMessage(messages.type), - [opts.saleType.value], - false, - [ - { - label: intl.formatMessage(messages.fixed), - value: DiscountValueTypeEnum.FIXED - }, - { - label: intl.formatMessage(messages.percentage), - value: DiscountValueTypeEnum.PERCENTAGE - } - ] - ), - active: opts.saleType.active - } - ]; -} - export function getFilterVariables( params: SaleListUrlFilters ): SaleFilterInput { diff --git a/src/discounts/views/SaleList/messages.ts b/src/discounts/views/SaleList/messages.ts deleted file mode 100644 index 70cb66cc9..000000000 --- a/src/discounts/views/SaleList/messages.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - active: { - defaultMessage: "Active", - description: "sale status" - }, - expired: { - defaultMessage: "Expired", - description: "sale status" - }, - fixed: { - defaultMessage: "Fixed amount", - description: "discount type" - }, - percentage: { - defaultMessage: "Percentage", - description: "discount type" - }, - scheduled: { - defaultMessage: "Scheduled", - description: "sale status" - }, - started: { - defaultMessage: "Started", - description: "sale start date" - }, - status: { - defaultMessage: "Status", - description: "sale status" - }, - type: { - defaultMessage: "Discount Type" - } -}); - -export default messages; From 7c69c81ce638ea8ade9272bef005494b02bbfd52 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 14:01:26 +0100 Subject: [PATCH 41/85] Separate visual and app layer in vouchers --- .../VoucherListPage/VoucherListPage.tsx | 10 +- .../components/VoucherListPage/filters.ts | 132 ++++++++++++++++++ .../components/VoucherListPage/index.ts | 1 + src/discounts/types.ts | 13 -- .../views/VoucherList/VoucherList.tsx | 2 +- .../VoucherList/{filter.ts => filters.ts} | 87 +----------- src/discounts/views/VoucherList/messages.ts | 41 ------ 7 files changed, 143 insertions(+), 143 deletions(-) create mode 100644 src/discounts/components/VoucherListPage/filters.ts rename src/discounts/views/VoucherList/{filter.ts => filters.ts} (65%) delete mode 100644 src/discounts/views/VoucherList/messages.ts diff --git a/src/discounts/components/VoucherListPage/VoucherListPage.tsx b/src/discounts/components/VoucherListPage/VoucherListPage.tsx index 947e11ffa..91dbf982d 100644 --- a/src/discounts/components/VoucherListPage/VoucherListPage.tsx +++ b/src/discounts/components/VoucherListPage/VoucherListPage.tsx @@ -15,13 +15,13 @@ import { FilterPageProps } from "@saleor/types"; import { VoucherListUrlSortField } from "@saleor/discounts/urls"; -import { - createFilterStructure, - VoucherFilterKeys -} from "@saleor/discounts/views/VoucherList/filter"; -import { VoucherListFilterOpts } from "@saleor/discounts/types"; import { VoucherList_vouchers_edges_node } from "../../types/VoucherList"; import VoucherList from "../VoucherList"; +import { + VoucherFilterKeys, + VoucherListFilterOpts, + createFilterStructure +} from "./filters"; export interface VoucherListPageProps extends PageListProps, diff --git a/src/discounts/components/VoucherListPage/filters.ts b/src/discounts/components/VoucherListPage/filters.ts new file mode 100644 index 000000000..4faa735cd --- /dev/null +++ b/src/discounts/components/VoucherListPage/filters.ts @@ -0,0 +1,132 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { + createOptionsField, + createNumberField, + createDateField +} from "@saleor/utils/filters/fields"; +import { + VoucherDiscountType, + DiscountStatusEnum, + DiscountValueTypeEnum +} from "@saleor/types/globalTypes"; +import { MinMax, FilterOpts } from "@saleor/types"; +import { IFilter } from "@saleor/components/Filter"; + +export enum VoucherFilterKeys { + saleType = "saleType", + started = "started", + status = "status", + timesUsed = "timesUsed" +} + +export interface VoucherListFilterOpts { + saleType: FilterOpts; + started: FilterOpts; + status: FilterOpts; + timesUsed: FilterOpts; +} + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "voucher status" + }, + expired: { + defaultMessage: "Expired", + description: "voucher status" + }, + fixed: { + defaultMessage: "Fixed amount", + description: "discount type" + }, + percentage: { + defaultMessage: "Percentage", + description: "discount type" + }, + scheduled: { + defaultMessage: "Scheduled", + description: "voucher status" + }, + started: { + defaultMessage: "Started", + description: "voucher start date" + }, + status: { + defaultMessage: "Status", + description: "voucher status" + }, + timesUsed: { + defaultMessage: "Times used", + description: "voucher" + }, + type: { + defaultMessage: "Discount Type" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: VoucherListFilterOpts +): IFilter { + return [ + { + ...createDateField( + VoucherFilterKeys.started, + intl.formatMessage(messages.started), + opts.started.value + ), + active: opts.started.active + }, + { + ...createNumberField( + VoucherFilterKeys.timesUsed, + intl.formatMessage(messages.timesUsed), + opts.timesUsed.value + ), + active: opts.timesUsed.active + }, + { + ...createOptionsField( + VoucherFilterKeys.status, + intl.formatMessage(messages.status), + opts.status.value, + true, + [ + { + label: intl.formatMessage(messages.active), + value: DiscountStatusEnum.ACTIVE + }, + { + label: intl.formatMessage(messages.expired), + value: DiscountStatusEnum.EXPIRED + }, + { + label: intl.formatMessage(messages.scheduled), + value: DiscountStatusEnum.SCHEDULED + } + ] + ), + active: opts.status.active + }, + { + ...createOptionsField( + VoucherFilterKeys.saleType, + intl.formatMessage(messages.type), + opts.saleType.value, + false, + [ + { + label: intl.formatMessage(messages.fixed), + value: DiscountValueTypeEnum.FIXED + }, + { + label: intl.formatMessage(messages.percentage), + value: DiscountValueTypeEnum.PERCENTAGE + } + ] + ), + active: opts.saleType.active + } + ]; +} diff --git a/src/discounts/components/VoucherListPage/index.ts b/src/discounts/components/VoucherListPage/index.ts index 84b3fa702..6736b0f16 100644 --- a/src/discounts/components/VoucherListPage/index.ts +++ b/src/discounts/components/VoucherListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./VoucherListPage"; export * from "./VoucherListPage"; +export * from "./filters"; diff --git a/src/discounts/types.ts b/src/discounts/types.ts index b51bc7313..9c15c2ed6 100644 --- a/src/discounts/types.ts +++ b/src/discounts/types.ts @@ -1,16 +1,3 @@ -import { - VoucherDiscountType, - DiscountStatusEnum -} from "@saleor/types/globalTypes"; -import { MinMax, FilterOpts } from "@saleor/types"; - -export interface VoucherListFilterOpts { - saleType: FilterOpts; - started: FilterOpts; - status: FilterOpts; - timesUsed: FilterOpts; -} - export enum RequirementsPicker { ORDER = "ORDER", ITEM = "ITEM", diff --git a/src/discounts/views/VoucherList/VoucherList.tsx b/src/discounts/views/VoucherList/VoucherList.tsx index e4b41841f..a517c45ac 100644 --- a/src/discounts/views/VoucherList/VoucherList.tsx +++ b/src/discounts/views/VoucherList/VoucherList.tsx @@ -45,7 +45,7 @@ import { saveFilterTab, getFilterQueryParam, getFilterOpts -} from "./filter"; +} from "./filters"; import { getSortQueryVariables } from "./sort"; interface VoucherListProps { diff --git a/src/discounts/views/VoucherList/filter.ts b/src/discounts/views/VoucherList/filters.ts similarity index 65% rename from src/discounts/views/VoucherList/filter.ts rename to src/discounts/views/VoucherList/filters.ts index acf149b80..6f17e8ba7 100644 --- a/src/discounts/views/VoucherList/filter.ts +++ b/src/discounts/views/VoucherList/filters.ts @@ -1,19 +1,14 @@ -import { IntlShape } from "react-intl"; - import { VoucherFilterInput, DiscountStatusEnum, - DiscountValueTypeEnum, VoucherDiscountType } from "@saleor/types/globalTypes"; import { maybe, findValueInEnum, joinDateTime } from "@saleor/misc"; -import { VoucherListFilterOpts } from "@saleor/discounts/types"; -import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { IFilterElement } from "@saleor/components/Filter"; import { - createDateField, - createOptionsField, - createNumberField -} from "@saleor/utils/filters/fields"; + VoucherListFilterOpts, + VoucherFilterKeys +} from "@saleor/discounts/components/VoucherListPage"; import { createFilterTabUtils, createFilterUtils, @@ -24,17 +19,9 @@ import { VoucherListUrlFiltersEnum, VoucherListUrlQueryParams } from "../../urls"; -import messages from "./messages"; export const VOUCHER_FILTERS_KEY = "voucherFilters"; -export enum VoucherFilterKeys { - saleType = "saleType", - started = "started", - status = "status", - timesUsed = "timesUsed" -} - export function getFilterOpts( params: VoucherListUrlFilters ): VoucherListFilterOpts { @@ -90,72 +77,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: VoucherListFilterOpts -): IFilter { - return [ - { - ...createDateField( - VoucherFilterKeys.started, - intl.formatMessage(messages.started), - opts.started.value - ), - active: opts.started.active - }, - { - ...createNumberField( - VoucherFilterKeys.timesUsed, - intl.formatMessage(messages.timesUsed), - opts.timesUsed.value - ), - active: opts.timesUsed.active - }, - { - ...createOptionsField( - VoucherFilterKeys.status, - intl.formatMessage(messages.status), - opts.status.value, - true, - [ - { - label: intl.formatMessage(messages.active), - value: DiscountStatusEnum.ACTIVE - }, - { - label: intl.formatMessage(messages.expired), - value: DiscountStatusEnum.EXPIRED - }, - { - label: intl.formatMessage(messages.scheduled), - value: DiscountStatusEnum.SCHEDULED - } - ] - ), - active: opts.status.active - }, - { - ...createOptionsField( - VoucherFilterKeys.saleType, - intl.formatMessage(messages.type), - opts.saleType.value, - false, - [ - { - label: intl.formatMessage(messages.fixed), - value: DiscountValueTypeEnum.FIXED - }, - { - label: intl.formatMessage(messages.percentage), - value: DiscountValueTypeEnum.PERCENTAGE - } - ] - ), - active: opts.saleType.active - } - ]; -} - export function getFilterVariables( params: VoucherListUrlFilters ): VoucherFilterInput { diff --git a/src/discounts/views/VoucherList/messages.ts b/src/discounts/views/VoucherList/messages.ts deleted file mode 100644 index df51e9142..000000000 --- a/src/discounts/views/VoucherList/messages.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - active: { - defaultMessage: "Active", - description: "voucher status" - }, - expired: { - defaultMessage: "Expired", - description: "voucher status" - }, - fixed: { - defaultMessage: "Fixed amount", - description: "discount type" - }, - percentage: { - defaultMessage: "Percentage", - description: "discount type" - }, - scheduled: { - defaultMessage: "Scheduled", - description: "voucher status" - }, - started: { - defaultMessage: "Started", - description: "voucher start date" - }, - status: { - defaultMessage: "Status", - description: "voucher status" - }, - timesUsed: { - defaultMessage: "Times used", - description: "voucher" - }, - type: { - defaultMessage: "Discount Type" - } -}); - -export default messages; From cde1735869af183391919a692282e7f36871c0d3 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 14:16:16 +0100 Subject: [PATCH 42/85] Separate visual and app layer in orders --- .../OrderListPage/OrderListPage.tsx | 10 +-- .../components/OrderListPage/filters.ts | 71 +++++++++++++++++++ src/orders/types.ts | 6 -- src/orders/views/OrderList/filters.ts | 67 ++--------------- src/orders/views/OrderList/messages.ts | 29 -------- 5 files changed, 81 insertions(+), 102 deletions(-) create mode 100644 src/orders/components/OrderListPage/filters.ts delete mode 100644 src/orders/views/OrderList/messages.ts diff --git a/src/orders/components/OrderListPage/OrderListPage.tsx b/src/orders/components/OrderListPage/OrderListPage.tsx index 635063153..525380a0d 100644 --- a/src/orders/components/OrderListPage/OrderListPage.tsx +++ b/src/orders/components/OrderListPage/OrderListPage.tsx @@ -14,14 +14,14 @@ import { SortPage } from "@saleor/types"; import { OrderListUrlSortField } from "@saleor/orders/urls"; -import { - OrderFilterKeys, - createFilterStructure -} from "@saleor/orders/views/OrderList/filters"; import FilterBar from "@saleor/components/FilterBar"; import { OrderList_orders_edges_node } from "../../types/OrderList"; import OrderList from "../OrderList"; -import { OrderListFilterOpts } from "../../types"; +import { + createFilterStructure, + OrderListFilterOpts, + OrderFilterKeys +} from "./filters"; export interface OrderListPageProps extends PageListProps, diff --git a/src/orders/components/OrderListPage/filters.ts b/src/orders/components/OrderListPage/filters.ts new file mode 100644 index 000000000..9e05c75d9 --- /dev/null +++ b/src/orders/components/OrderListPage/filters.ts @@ -0,0 +1,71 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { FilterOpts, MinMax } from "@saleor/types"; +import { OrderStatusFilter } from "@saleor/types/globalTypes"; +import { + createDateField, + createOptionsField +} from "@saleor/utils/filters/fields"; +import { IFilter } from "@saleor/components/Filter"; +import { orderStatusMessages } from "@saleor/misc"; +import { commonMessages } from "@saleor/intl"; + +export enum OrderFilterKeys { + created = "created", + status = "status" +} + +export interface OrderListFilterOpts { + created: FilterOpts; + status: FilterOpts; +} + +const messages = defineMessages({ + placed: { + defaultMessage: "Placed", + description: "order" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: OrderListFilterOpts +): IFilter { + return [ + { + ...createDateField( + OrderFilterKeys.created, + intl.formatMessage(messages.placed), + opts.created.value + ), + active: opts.created.active + }, + { + ...createOptionsField( + OrderFilterKeys.status, + intl.formatMessage(commonMessages.status), + opts.status.value, + true, + [ + { + label: intl.formatMessage(orderStatusMessages.cancelled), + value: OrderStatusFilter.CANCELED + }, + { + label: intl.formatMessage(orderStatusMessages.fulfilled), + value: OrderStatusFilter.FULFILLED + }, + { + label: intl.formatMessage(orderStatusMessages.partiallyFulfilled), + value: OrderStatusFilter.PARTIALLY_FULFILLED + }, + { + label: intl.formatMessage(orderStatusMessages.unfulfilled), + value: OrderStatusFilter.UNFULFILLED + } + ] + ), + active: opts.status.active + } + ]; +} diff --git a/src/orders/types.ts b/src/orders/types.ts index c544e4984..e6fbd6d44 100644 --- a/src/orders/types.ts +++ b/src/orders/types.ts @@ -1,10 +1,4 @@ import { FilterOpts, MinMax } from "@saleor/types"; -import { OrderStatusFilter } from "@saleor/types/globalTypes"; - -export interface OrderListFilterOpts { - created: FilterOpts; - status: FilterOpts; -} export interface OrderDraftListFilterOpts { created: FilterOpts; diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index 7e8e0629a..28b549c7f 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -1,16 +1,9 @@ -import { IntlShape } from "react-intl"; - +import { findInEnum, maybe, findValueInEnum } from "@saleor/misc"; import { - findInEnum, - maybe, - orderStatusMessages, - findValueInEnum -} from "@saleor/misc"; -import { - createDateField, - createOptionsField -} from "@saleor/utils/filters/fields"; -import { IFilter, IFilterElement } from "../../../components/Filter"; + OrderListFilterOpts, + OrderFilterKeys +} from "@saleor/orders/components/OrderListPage/filters"; +import { IFilterElement } from "../../../components/Filter"; import { OrderFilterInput, OrderStatusFilter, @@ -27,16 +20,9 @@ import { OrderListUrlFiltersWithMultipleValuesEnum, OrderListUrlQueryParams } from "../../urls"; -import { OrderListFilterOpts } from "../../types"; -import messages from "./messages"; export const ORDER_FILTERS_KEY = "orderFilters"; -export enum OrderFilterKeys { - created = "created", - status = "status" -} - export function getFilterOpts( params: OrderListUrlFilters ): OrderListFilterOpts { @@ -69,49 +55,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: OrderListFilterOpts -): IFilter { - return [ - { - ...createDateField( - OrderFilterKeys.created, - intl.formatMessage(messages.placed), - opts.created.value - ), - active: opts.created.active - }, - { - ...createOptionsField( - OrderFilterKeys.status, - intl.formatMessage(messages.status), - opts.status.value, - true, - [ - { - label: intl.formatMessage(orderStatusMessages.cancelled), - value: OrderStatusFilter.CANCELED - }, - { - label: intl.formatMessage(orderStatusMessages.fulfilled), - value: OrderStatusFilter.FULFILLED - }, - { - label: intl.formatMessage(orderStatusMessages.partiallyFulfilled), - value: OrderStatusFilter.PARTIALLY_FULFILLED - }, - { - label: intl.formatMessage(orderStatusMessages.unfulfilled), - value: OrderStatusFilter.UNFULFILLED - } - ] - ), - active: opts.status.active - } - ]; -} - export function getFilterVariables( params: OrderListUrlFilters ): OrderFilterInput { diff --git a/src/orders/views/OrderList/messages.ts b/src/orders/views/OrderList/messages.ts deleted file mode 100644 index 52dd258ae..000000000 --- a/src/orders/views/OrderList/messages.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - fulfilled: { - defaultMessage: "Fulfilled", - description: "order status" - }, - partiallyFulfilled: { - defaultMessage: "Partially Fulfilled", - description: "order status" - }, - placed: { - defaultMessage: "Placed", - description: "order" - }, - readyToCapture: { - defaultMessage: "Ready to Capture", - description: "order status" - }, - status: { - defaultMessage: "Order Status" - }, - unfulfilled: { - defaultMessage: "Unfulfilled", - description: "order status" - } -}); - -export default messages; From db60f13216e125a45fab39c599b5bbacf67dcca7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 14:20:03 +0100 Subject: [PATCH 43/85] Separate visual and app layer in drafts --- .../OrderDraftListPage/OrderDraftListPage.tsx | 10 ++-- .../components/OrderDraftListPage/filters.ts | 50 +++++++++++++++++++ .../components/OrderDraftListPage/index.ts | 1 + src/orders/types.ts | 6 --- .../views/OrderDraftList/OrderDraftList.tsx | 2 +- .../OrderDraftList/{filter.ts => filters.ts} | 40 ++------------- src/orders/views/OrderDraftList/messages.ts | 14 ------ 7 files changed, 62 insertions(+), 61 deletions(-) create mode 100644 src/orders/components/OrderDraftListPage/filters.ts delete mode 100644 src/orders/types.ts rename src/orders/views/OrderDraftList/{filter.ts => filters.ts} (70%) delete mode 100644 src/orders/views/OrderDraftList/messages.ts diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index 921fe553e..83b6ec50d 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -15,14 +15,14 @@ import { FilterPageProps } from "@saleor/types"; import { OrderDraftListUrlSortField } from "@saleor/orders/urls"; -import { - OrderDraftFilterKeys, - createFilterStructure -} from "@saleor/orders/views/OrderDraftList/filter"; -import { OrderDraftListFilterOpts } from "@saleor/orders/types"; import FilterBar from "@saleor/components/FilterBar"; import OrderDraftList from "../OrderDraftList"; import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList"; +import { + OrderDraftListFilterOpts, + OrderDraftFilterKeys, + createFilterStructure +} from "./filters"; export interface OrderDraftListPageProps extends PageListProps, diff --git a/src/orders/components/OrderDraftListPage/filters.ts b/src/orders/components/OrderDraftListPage/filters.ts new file mode 100644 index 000000000..9824461f8 --- /dev/null +++ b/src/orders/components/OrderDraftListPage/filters.ts @@ -0,0 +1,50 @@ +import { IntlShape, defineMessages } from "react-intl"; + +import { FilterOpts, MinMax } from "@saleor/types"; +import { createDateField, createTextField } from "@saleor/utils/filters/fields"; +import { IFilter } from "@saleor/components/Filter"; + +export enum OrderDraftFilterKeys { + created = "created", + customer = "customer" +} + +export interface OrderDraftListFilterOpts { + created: FilterOpts; + customer: FilterOpts; +} + +const messages = defineMessages({ + created: { + defaultMessage: "Created", + description: "draft order" + }, + customer: { + defaultMessage: "Customer", + description: "draft order" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: OrderDraftListFilterOpts +): IFilter { + return [ + { + ...createDateField( + OrderDraftFilterKeys.created, + intl.formatMessage(messages.created), + opts.created.value + ), + active: opts.created.active + }, + { + ...createTextField( + OrderDraftFilterKeys.customer, + intl.formatMessage(messages.customer), + opts.customer.value + ), + active: opts.customer.active + } + ]; +} diff --git a/src/orders/components/OrderDraftListPage/index.ts b/src/orders/components/OrderDraftListPage/index.ts index de5e8daab..93b2da611 100644 --- a/src/orders/components/OrderDraftListPage/index.ts +++ b/src/orders/components/OrderDraftListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./OrderDraftListPage"; export * from "./OrderDraftListPage"; +export * from "./filters"; diff --git a/src/orders/types.ts b/src/orders/types.ts deleted file mode 100644 index e6fbd6d44..000000000 --- a/src/orders/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FilterOpts, MinMax } from "@saleor/types"; - -export interface OrderDraftListFilterOpts { - created: FilterOpts; - customer: FilterOpts; -} diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index b4965c480..81e816d9a 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -46,7 +46,7 @@ import { saveFilterTab, getFilterQueryParam, getFilterOpts -} from "./filter"; +} from "./filters"; import { getSortQueryVariables } from "./sort"; interface OrderDraftListProps { diff --git a/src/orders/views/OrderDraftList/filter.ts b/src/orders/views/OrderDraftList/filters.ts similarity index 70% rename from src/orders/views/OrderDraftList/filter.ts rename to src/orders/views/OrderDraftList/filters.ts index 034952559..151583a0e 100644 --- a/src/orders/views/OrderDraftList/filter.ts +++ b/src/orders/views/OrderDraftList/filters.ts @@ -1,10 +1,10 @@ -import { IntlShape } from "react-intl"; - import { OrderDraftFilterInput } from "@saleor/types/globalTypes"; import { maybe } from "@saleor/misc"; -import { OrderDraftListFilterOpts } from "@saleor/orders/types"; -import { IFilter, IFilterElement } from "@saleor/components/Filter"; -import { createTextField, createDateField } from "@saleor/utils/filters/fields"; +import { IFilterElement } from "@saleor/components/Filter"; +import { + OrderDraftFilterKeys, + OrderDraftListFilterOpts +} from "@saleor/orders/components/OrderDraftListPage"; import { OrderDraftListUrlFilters, OrderDraftListUrlFiltersEnum, @@ -14,15 +14,9 @@ import { createFilterTabUtils, createFilterUtils } from "../../../utils/filters"; -import messages from "./messages"; export const ORDER_DRAFT_FILTERS_KEY = "orderDraftFilters"; -export enum OrderDraftFilterKeys { - created = "created", - customer = "customer" -} - export function getFilterOpts( params: OrderDraftListUrlFilters ): OrderDraftListFilterOpts { @@ -47,30 +41,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: OrderDraftListFilterOpts -): IFilter { - return [ - { - ...createDateField( - OrderDraftFilterKeys.created, - intl.formatMessage(messages.created), - opts.created.value - ), - active: opts.created.active - }, - { - ...createTextField( - OrderDraftFilterKeys.customer, - intl.formatMessage(messages.customer), - opts.customer.value - ), - active: opts.customer.active - } - ]; -} - export function getFilterVariables( params: OrderDraftListUrlFilters ): OrderDraftFilterInput { diff --git a/src/orders/views/OrderDraftList/messages.ts b/src/orders/views/OrderDraftList/messages.ts deleted file mode 100644 index 6403bbd02..000000000 --- a/src/orders/views/OrderDraftList/messages.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - customer: { - defaultMessage: "Customer", - description: "draft order" - }, - created: { - defaultMessage: "Created", - description: "draft order" - } -}); - -export default messages; From 15849ff2afb2f070988e7293509958882ae2dede Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 14:29:08 +0100 Subject: [PATCH 44/85] Separate visual and app layer in products --- .../ProductListPage/ProductListPage.tsx | 8 +- .../components/ProductListPage/filters.ts | 110 ++++++++++++++++++ .../components/ProductListPage/index.ts | 1 + src/products/types.ts | 13 --- src/products/views/ProductList/filters.ts | 73 +----------- src/products/views/ProductList/messages.ts | 33 ------ .../stories/products/ProductListPage.tsx | 4 +- 7 files changed, 122 insertions(+), 120 deletions(-) create mode 100644 src/products/components/ProductListPage/filters.ts delete mode 100644 src/products/types.ts delete mode 100644 src/products/views/ProductList/messages.ts diff --git a/src/products/components/ProductListPage/ProductListPage.tsx b/src/products/components/ProductListPage/ProductListPage.tsx index 074952648..d0bb14c1d 100644 --- a/src/products/components/ProductListPage/ProductListPage.tsx +++ b/src/products/components/ProductListPage/ProductListPage.tsx @@ -24,13 +24,13 @@ import { SortPage } from "@saleor/types"; import FilterBar from "@saleor/components/FilterBar"; -import { ProductListFilterOpts } from "@saleor/products/types"; import { ProductListUrlSortField } from "../../urls"; +import ProductList from "../ProductList"; import { createFilterStructure, - ProductFilterKeys -} from "../../views/ProductList/filters"; -import ProductList from "../ProductList"; + ProductFilterKeys, + ProductListFilterOpts +} from "./filters"; export interface ProductListPageProps extends PageListProps, diff --git a/src/products/components/ProductListPage/filters.ts b/src/products/components/ProductListPage/filters.ts new file mode 100644 index 000000000..3f65c2211 --- /dev/null +++ b/src/products/components/ProductListPage/filters.ts @@ -0,0 +1,110 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { FilterOpts, MinMax } from "@saleor/types"; +import { StockAvailability } from "@saleor/types/globalTypes"; +import { + createOptionsField, + createPriceField +} from "@saleor/utils/filters/fields"; +import { IFilter } from "@saleor/components/Filter"; + +export enum ProductFilterKeys { + status = "status", + price = "price", + stock = "stock" +} + +export interface ProductListFilterOpts { + price: FilterOpts; + status: FilterOpts; + stockStatus: FilterOpts; +} + +export enum ProductStatus { + PUBLISHED = "published", + HIDDEN = "hidden" +} + +const messages = defineMessages({ + available: { + defaultMessage: "Available", + description: "product status" + }, + hidden: { + defaultMessage: "Hidden", + description: "product is hidden" + }, + outOfStock: { + defaultMessage: "Out Of Stock", + description: "product status" + }, + price: { + defaultMessage: "Price" + }, + quantity: { + defaultMessage: "Stock quantity", + description: "product" + }, + visibility: { + defaultMessage: "Visibility", + description: "product visibility" + }, + visible: { + defaultMessage: "Visible", + description: "product is visible" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: ProductListFilterOpts +): IFilter { + return [ + { + ...createOptionsField( + ProductFilterKeys.status, + intl.formatMessage(messages.visibility), + [opts.status.value], + false, + [ + { + label: intl.formatMessage(messages.visible), + value: ProductStatus.PUBLISHED + }, + { + label: intl.formatMessage(messages.hidden), + value: ProductStatus.HIDDEN + } + ] + ), + active: opts.status.active + }, + { + ...createOptionsField( + ProductFilterKeys.stock, + intl.formatMessage(messages.quantity), + [opts.stockStatus.value], + false, + [ + { + label: intl.formatMessage(messages.available), + value: StockAvailability.IN_STOCK + }, + { + label: intl.formatMessage(messages.outOfStock), + value: StockAvailability.OUT_OF_STOCK + } + ] + ), + active: opts.stockStatus.active + }, + { + ...createPriceField( + ProductFilterKeys.price, + intl.formatMessage(messages.price), + opts.price.value + ), + active: opts.price.active + } + ]; +} diff --git a/src/products/components/ProductListPage/index.ts b/src/products/components/ProductListPage/index.ts index a5e54aa30..ceb0354d3 100644 --- a/src/products/components/ProductListPage/index.ts +++ b/src/products/components/ProductListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./ProductListPage"; export * from "./ProductListPage"; +export * from "./filters"; diff --git a/src/products/types.ts b/src/products/types.ts deleted file mode 100644 index f6dfd9396..000000000 --- a/src/products/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FilterOpts, MinMax } from "@saleor/types"; -import { StockAvailability } from "@saleor/types/globalTypes"; - -export enum ProductStatus { - PUBLISHED = "published", - HIDDEN = "hidden" -} - -export interface ProductListFilterOpts { - price: FilterOpts; - status: FilterOpts; - stockStatus: FilterOpts; -} diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index e3fd579ed..476208e0d 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -1,12 +1,10 @@ -import { IntlShape } from "react-intl"; - import { maybe, findValueInEnum } from "@saleor/misc"; import { - createOptionsField, - createPriceField -} from "@saleor/utils/filters/fields"; -import { ProductStatus, ProductListFilterOpts } from "@saleor/products/types"; -import { IFilterElement, IFilter } from "../../../components/Filter"; + ProductFilterKeys, + ProductListFilterOpts, + ProductStatus +} from "@saleor/products/components/ProductListPage"; +import { IFilterElement } from "../../../components/Filter"; import { ProductFilterInput, StockAvailability @@ -20,16 +18,9 @@ import { ProductListUrlFiltersEnum, ProductListUrlQueryParams } from "../../urls"; -import messages from "./messages"; export const PRODUCT_FILTERS_KEY = "productFilters"; -export enum ProductFilterKeys { - status = "status", - price = "price", - stock = "stock" -} - export function getFilterOpts( params: ProductListUrlFilters ): ProductListFilterOpts { @@ -56,60 +47,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: ProductListFilterOpts -): IFilter { - return [ - { - ...createOptionsField( - ProductFilterKeys.status, - intl.formatMessage(messages.visibility), - [opts.status.value], - false, - [ - { - label: intl.formatMessage(messages.visible), - value: ProductStatus.PUBLISHED - }, - { - label: intl.formatMessage(messages.hidden), - value: ProductStatus.HIDDEN - } - ] - ), - active: opts.status.active - }, - { - ...createOptionsField( - ProductFilterKeys.stock, - intl.formatMessage(messages.quantity), - [opts.stockStatus.value], - false, - [ - { - label: intl.formatMessage(messages.available), - value: StockAvailability.IN_STOCK - }, - { - label: intl.formatMessage(messages.outOfStock), - value: StockAvailability.OUT_OF_STOCK - } - ] - ), - active: opts.stockStatus.active - }, - { - ...createPriceField( - ProductFilterKeys.price, - intl.formatMessage(messages.price), - opts.price.value - ), - active: opts.price.active - } - ]; -} - export function getFilterVariables( params: ProductListUrlFilters ): ProductFilterInput { diff --git a/src/products/views/ProductList/messages.ts b/src/products/views/ProductList/messages.ts deleted file mode 100644 index 236534926..000000000 --- a/src/products/views/ProductList/messages.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - available: { - defaultMessage: "Available", - description: "product status" - }, - hidden: { - defaultMessage: "Hidden", - description: "product is hidden" - }, - outOfStock: { - defaultMessage: "Out Of Stock", - description: "product status" - }, - price: { - defaultMessage: "Price" - }, - quantity: { - defaultMessage: "Stock quantity", - description: "product" - }, - visibility: { - defaultMessage: "Visibility", - description: "product visibility" - }, - visible: { - defaultMessage: "Visible", - description: "product is visible" - } -}); - -export default messages; diff --git a/src/storybook/stories/products/ProductListPage.tsx b/src/storybook/stories/products/ProductListPage.tsx index 7af840523..4e6cc84fb 100644 --- a/src/storybook/stories/products/ProductListPage.tsx +++ b/src/storybook/stories/products/ProductListPage.tsx @@ -7,7 +7,6 @@ import { products as productListFixture } from "@saleor/products/fixtures"; import { ProductListUrlSortField } from "@saleor/products/urls"; import { attributes } from "@saleor/productTypes/fixtures"; import { ListViews } from "@saleor/types"; -import { ProductStatus } from "@saleor/products/types"; import { StockAvailability } from "@saleor/types/globalTypes"; import { fetchMoreProps, @@ -17,7 +16,8 @@ import { sortPageProps } from "../../../fixtures"; import ProductListPage, { - ProductListPageProps + ProductListPageProps, + ProductStatus } from "../../../products/components/ProductListPage"; import Decorator from "../../Decorator"; From 0fb8a66b9ab7e022759d007da9b65ce07cd401ae Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 14:34:04 +0100 Subject: [PATCH 45/85] Separate visual and app layer in product types --- .../ProductTypeListPage.tsx | 10 +-- .../components/ProductTypeListPage/filters.ts | 85 +++++++++++++++++++ .../components/ProductTypeListPage/index.ts | 1 + src/productTypes/types.ts | 10 --- .../views/ProductTypeList/ProductTypeList.tsx | 2 +- .../ProductTypeList/{filter.ts => filters.ts} | 59 +------------ .../views/ProductTypeList/messages.ts | 22 ----- 7 files changed, 94 insertions(+), 95 deletions(-) create mode 100644 src/productTypes/components/ProductTypeListPage/filters.ts delete mode 100644 src/productTypes/types.ts rename src/productTypes/views/ProductTypeList/{filter.ts => filters.ts} (59%) delete mode 100644 src/productTypes/views/ProductTypeList/messages.ts diff --git a/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx b/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx index af9996645..647ae7ada 100644 --- a/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx +++ b/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx @@ -9,11 +9,6 @@ import PageHeader from "@saleor/components/PageHeader"; import FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls"; -import { - ProductTypeFilterKeys, - createFilterStructure -} from "@saleor/productTypes/views/ProductTypeList/filter"; -import { ProductTypeListFilterOpts } from "@saleor/productTypes/types"; import { ListActions, PageListProps, @@ -23,6 +18,11 @@ import { } from "../../../types"; import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList"; import ProductTypeList from "../ProductTypeList"; +import { + createFilterStructure, + ProductTypeFilterKeys, + ProductTypeListFilterOpts +} from "./filters"; export interface ProductTypeListPageProps extends PageListProps, diff --git a/src/productTypes/components/ProductTypeListPage/filters.ts b/src/productTypes/components/ProductTypeListPage/filters.ts new file mode 100644 index 000000000..c6664e09c --- /dev/null +++ b/src/productTypes/components/ProductTypeListPage/filters.ts @@ -0,0 +1,85 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { FilterOpts } from "@saleor/types"; +import { + ProductTypeConfigurable, + ProductTypeEnum +} from "@saleor/types/globalTypes"; +import { IFilter } from "@saleor/components/Filter"; +import { commonMessages } from "@saleor/intl"; +import { createOptionsField } from "@saleor/utils/filters/fields"; + +export enum ProductTypeFilterKeys { + configurable = "configurable", + type = "type" +} + +export interface ProductTypeListFilterOpts { + configurable: FilterOpts; + type: FilterOpts; +} + +const messages = defineMessages({ + configurable: { + defaultMessage: "Configurable", + description: "product type" + }, + digital: { + defaultMessage: "Digital", + description: "product" + }, + shippable: { + defaultMessage: "Shippable", + description: "product" + }, + type: { + defaultMessage: "Type", + description: "product type is digital or physical" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: ProductTypeListFilterOpts +): IFilter { + return [ + { + ...createOptionsField( + ProductTypeFilterKeys.configurable, + intl.formatMessage(messages.configurable), + [opts.configurable.value], + false, + [ + { + label: intl.formatMessage(commonMessages.yes), + value: ProductTypeConfigurable.CONFIGURABLE + }, + { + label: intl.formatMessage(commonMessages.no), + value: ProductTypeConfigurable.SIMPLE + } + ] + ), + active: opts.configurable.active + }, + { + ...createOptionsField( + ProductTypeFilterKeys.type, + intl.formatMessage(messages.type), + [opts.type.value], + false, + [ + { + label: intl.formatMessage(messages.digital), + value: ProductTypeEnum.DIGITAL + }, + { + label: intl.formatMessage(messages.shippable), + value: ProductTypeEnum.SHIPPABLE + } + ] + ), + active: opts.type.active + } + ]; +} diff --git a/src/productTypes/components/ProductTypeListPage/index.ts b/src/productTypes/components/ProductTypeListPage/index.ts index 55c8646cc..0e7b89ba0 100644 --- a/src/productTypes/components/ProductTypeListPage/index.ts +++ b/src/productTypes/components/ProductTypeListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./ProductTypeListPage"; export * from "./ProductTypeListPage"; +export * from "./filters"; diff --git a/src/productTypes/types.ts b/src/productTypes/types.ts deleted file mode 100644 index 5ecaf8b51..000000000 --- a/src/productTypes/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FilterOpts } from "@saleor/types"; -import { - ProductTypeConfigurable, - ProductTypeEnum -} from "@saleor/types/globalTypes"; - -export interface ProductTypeListFilterOpts { - configurable: FilterOpts; - type: FilterOpts; -} diff --git a/src/productTypes/views/ProductTypeList/ProductTypeList.tsx b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx index 0e088818b..1de7f7498 100644 --- a/src/productTypes/views/ProductTypeList/ProductTypeList.tsx +++ b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx @@ -45,7 +45,7 @@ import { saveFilterTab, getFilterQueryParam, getFilterOpts -} from "./filter"; +} from "./filters"; import { getSortQueryVariables } from "./sort"; interface ProductTypeListProps { diff --git a/src/productTypes/views/ProductTypeList/filter.ts b/src/productTypes/views/ProductTypeList/filters.ts similarity index 59% rename from src/productTypes/views/ProductTypeList/filter.ts rename to src/productTypes/views/ProductTypeList/filters.ts index 8d542c374..096f146ca 100644 --- a/src/productTypes/views/ProductTypeList/filter.ts +++ b/src/productTypes/views/ProductTypeList/filters.ts @@ -1,14 +1,11 @@ -import { IntlShape } from "react-intl"; - import { ProductTypeFilterInput, ProductTypeConfigurable, ProductTypeEnum } from "@saleor/types/globalTypes"; -import { IFilterElement, IFilter } from "@saleor/components/Filter"; +import { IFilterElement } from "@saleor/components/Filter"; import { maybe, findValueInEnum } from "@saleor/misc"; -import { createOptionsField } from "@saleor/utils/filters/fields"; -import { commonMessages } from "@saleor/intl"; +import { ProductTypeFilterKeys } from "@saleor/productTypes/components/ProductTypeListPage"; import { createFilterTabUtils, createFilterUtils @@ -19,15 +16,9 @@ import { ProductTypeListUrlQueryParams } from "../../urls"; import { ProductTypeListFilterOpts } from "../../types"; -import messages from "./messages"; export const PRODUCT_TYPE_FILTERS_KEY = "productTypeFilters"; -export enum ProductTypeFilterKeys { - configurable = "configurable", - type = "type" -} - export function getFilterOpts( params: ProductTypeListUrlFilters ): ProductTypeListFilterOpts { @@ -45,52 +36,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: ProductTypeListFilterOpts -): IFilter { - return [ - { - ...createOptionsField( - ProductTypeFilterKeys.configurable, - intl.formatMessage(messages.configurable), - [opts.configurable.value], - false, - [ - { - label: intl.formatMessage(commonMessages.yes), - value: ProductTypeConfigurable.CONFIGURABLE - }, - { - label: intl.formatMessage(commonMessages.no), - value: ProductTypeConfigurable.SIMPLE - } - ] - ), - active: opts.configurable.active - }, - { - ...createOptionsField( - ProductTypeFilterKeys.type, - intl.formatMessage(messages.type), - [opts.type.value], - false, - [ - { - label: intl.formatMessage(messages.digital), - value: ProductTypeEnum.DIGITAL - }, - { - label: intl.formatMessage(messages.shippable), - value: ProductTypeEnum.SHIPPABLE - } - ] - ), - active: opts.type.active - } - ]; -} - export function getFilterVariables( params: ProductTypeListUrlFilters ): ProductTypeFilterInput { diff --git a/src/productTypes/views/ProductTypeList/messages.ts b/src/productTypes/views/ProductTypeList/messages.ts deleted file mode 100644 index e5b3e3744..000000000 --- a/src/productTypes/views/ProductTypeList/messages.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - configurable: { - defaultMessage: "Configurable", - description: "product type" - }, - digital: { - defaultMessage: "Digital", - description: "product" - }, - shippable: { - defaultMessage: "Shippable", - description: "product" - }, - type: { - defaultMessage: "Type", - description: "product type is digital or physical" - } -}); - -export default messages; From f6055709cbd170b54d7b51f3eabb79a4eab0683d Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 14:37:04 +0100 Subject: [PATCH 46/85] Separate visual and app layer in staff members --- .../views/ProductTypeList/filters.ts | 6 +- .../StaffListPage/StaffListPage.tsx | 10 ++-- src/staff/components/StaffListPage/filters.ts | 56 +++++++++++++++++++ src/staff/components/StaffListPage/index.ts | 1 + src/staff/types.ts | 6 -- src/staff/views/StaffList/StaffList.tsx | 2 +- .../views/StaffList/{filter.ts => filters.ts} | 42 ++------------ src/staff/views/StaffList/messages.ts | 18 ------ 8 files changed, 72 insertions(+), 69 deletions(-) create mode 100644 src/staff/components/StaffListPage/filters.ts delete mode 100644 src/staff/types.ts rename src/staff/views/StaffList/{filter.ts => filters.ts} (61%) delete mode 100644 src/staff/views/StaffList/messages.ts diff --git a/src/productTypes/views/ProductTypeList/filters.ts b/src/productTypes/views/ProductTypeList/filters.ts index 096f146ca..9655679ee 100644 --- a/src/productTypes/views/ProductTypeList/filters.ts +++ b/src/productTypes/views/ProductTypeList/filters.ts @@ -5,7 +5,10 @@ import { } from "@saleor/types/globalTypes"; import { IFilterElement } from "@saleor/components/Filter"; import { maybe, findValueInEnum } from "@saleor/misc"; -import { ProductTypeFilterKeys } from "@saleor/productTypes/components/ProductTypeListPage"; +import { + ProductTypeFilterKeys, + ProductTypeListFilterOpts +} from "@saleor/productTypes/components/ProductTypeListPage"; import { createFilterTabUtils, createFilterUtils @@ -15,7 +18,6 @@ import { ProductTypeListUrlFiltersEnum, ProductTypeListUrlQueryParams } from "../../urls"; -import { ProductTypeListFilterOpts } from "../../types"; export const PRODUCT_TYPE_FILTERS_KEY = "productTypeFilters"; diff --git a/src/staff/components/StaffListPage/StaffListPage.tsx b/src/staff/components/StaffListPage/StaffListPage.tsx index 8182657b2..b4faf0728 100644 --- a/src/staff/components/StaffListPage/StaffListPage.tsx +++ b/src/staff/components/StaffListPage/StaffListPage.tsx @@ -15,13 +15,13 @@ import { SortPage } from "@saleor/types"; import { StaffListUrlSortField } from "@saleor/staff/urls"; -import { - StaffFilterKeys, - createFilterStructure -} from "@saleor/staff/views/StaffList/filter"; -import { StaffListFilterOpts } from "@saleor/staff/types"; import { StaffList_staffUsers_edges_node } from "../../types/StaffList"; import StaffList from "../StaffList/StaffList"; +import { + createFilterStructure, + StaffFilterKeys, + StaffListFilterOpts +} from "./filters"; export interface StaffListPageProps extends ListProps, diff --git a/src/staff/components/StaffListPage/filters.ts b/src/staff/components/StaffListPage/filters.ts new file mode 100644 index 000000000..1831f09e2 --- /dev/null +++ b/src/staff/components/StaffListPage/filters.ts @@ -0,0 +1,56 @@ +import { IntlShape, defineMessages } from "react-intl"; + +import { FilterOpts } from "@saleor/types"; +import { StaffMemberStatus } from "@saleor/types/globalTypes"; +import { IFilter } from "@saleor/components/Filter"; +import { createOptionsField } from "@saleor/utils/filters/fields"; + +export enum StaffFilterKeys { + status = "status" +} + +export interface StaffListFilterOpts { + status: FilterOpts; +} + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "staff member's account" + }, + deactivated: { + defaultMessage: "Deactivated", + description: "staff member's account" + }, + status: { + defaultMessage: "Status", + description: "staff member's account" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: StaffListFilterOpts +): IFilter { + return [ + { + ...createOptionsField( + StaffFilterKeys.status, + intl.formatMessage(messages.status), + [opts.status.value], + false, + [ + { + label: intl.formatMessage(messages.active), + value: StaffMemberStatus.ACTIVE + }, + { + label: intl.formatMessage(messages.deactivated), + value: StaffMemberStatus.DEACTIVATED + } + ] + ), + active: opts.status.active + } + ]; +} diff --git a/src/staff/components/StaffListPage/index.ts b/src/staff/components/StaffListPage/index.ts index acbe4f3df..de24e5b17 100644 --- a/src/staff/components/StaffListPage/index.ts +++ b/src/staff/components/StaffListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./StaffListPage"; export * from "./StaffListPage"; +export * from "./filters"; diff --git a/src/staff/types.ts b/src/staff/types.ts deleted file mode 100644 index 20a0d9d9c..000000000 --- a/src/staff/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FilterOpts } from "@saleor/types"; -import { StaffMemberStatus } from "@saleor/types/globalTypes"; - -export interface StaffListFilterOpts { - status: FilterOpts; -} diff --git a/src/staff/views/StaffList/StaffList.tsx b/src/staff/views/StaffList/StaffList.tsx index 8967a283c..393bba370 100644 --- a/src/staff/views/StaffList/StaffList.tsx +++ b/src/staff/views/StaffList/StaffList.tsx @@ -46,7 +46,7 @@ import { saveFilterTab, getFilterQueryParam, getFilterOpts -} from "./filter"; +} from "./filters"; import { getSortQueryVariables } from "./sort"; interface StaffListProps { diff --git a/src/staff/views/StaffList/filter.ts b/src/staff/views/StaffList/filters.ts similarity index 61% rename from src/staff/views/StaffList/filter.ts rename to src/staff/views/StaffList/filters.ts index 0adaddf51..c26bf5723 100644 --- a/src/staff/views/StaffList/filter.ts +++ b/src/staff/views/StaffList/filters.ts @@ -1,9 +1,10 @@ -import { IntlShape } from "react-intl"; - import { StaffUserInput, StaffMemberStatus } from "@saleor/types/globalTypes"; import { maybe, findValueInEnum } from "@saleor/misc"; -import { IFilter, IFilterElement } from "@saleor/components/Filter"; -import { createOptionsField } from "@saleor/utils/filters/fields"; +import { IFilterElement } from "@saleor/components/Filter"; +import { + StaffListFilterOpts, + StaffFilterKeys +} from "@saleor/staff/components/StaffListPage"; import { createFilterTabUtils, createFilterUtils @@ -13,15 +14,9 @@ import { StaffListUrlFiltersEnum, StaffListUrlQueryParams } from "../../urls"; -import { StaffListFilterOpts } from "../../types"; -import messages from "./messages"; export const STAFF_FILTERS_KEY = "staffFilters"; -export enum StaffFilterKeys { - status = "status" -} - export function getFilterOpts( params: StaffListUrlFilters ): StaffListFilterOpts { @@ -33,33 +28,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: StaffListFilterOpts -): IFilter { - return [ - { - ...createOptionsField( - StaffFilterKeys.status, - intl.formatMessage(messages.status), - [opts.status.value], - false, - [ - { - label: intl.formatMessage(messages.active), - value: StaffMemberStatus.ACTIVE - }, - { - label: intl.formatMessage(messages.deactivated), - value: StaffMemberStatus.DEACTIVATED - } - ] - ), - active: opts.status.active - } - ]; -} - export function getFilterVariables( params: StaffListUrlFilters ): StaffUserInput { diff --git a/src/staff/views/StaffList/messages.ts b/src/staff/views/StaffList/messages.ts deleted file mode 100644 index a6d6f38bb..000000000 --- a/src/staff/views/StaffList/messages.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - active: { - defaultMessage: "Active", - description: "staff member's account" - }, - deactivated: { - defaultMessage: "Deactivated", - description: "staff member's account" - }, - status: { - defaultMessage: "Status", - description: "staff member's account" - } -}); - -export default messages; From 04a633bd32965461f98b62e7ede46f05a6d50203 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 12:34:38 +0100 Subject: [PATCH 47/85] Add boolean field --- src/components/Filter/FilterContent.tsx | 37 +++++++++++++++++++++++++ src/components/Filter/types.ts | 1 + src/utils/filters/fields.ts | 26 +++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index f9cf27caa..d0eefc5a6 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -1,9 +1,11 @@ import Button from "@material-ui/core/Button"; import Paper from "@material-ui/core/Paper"; +import Radio from "@material-ui/core/Radio"; import Typography from "@material-ui/core/Typography"; import FormControlLabel from "@material-ui/core/FormControlLabel"; import React from "react"; import { FormattedMessage, useIntl, IntlShape } from "react-intl"; +import classNames from "classnames"; import makeStyles from "@material-ui/core/styles/makeStyles"; import { fade } from "@material-ui/core/styles/colorManipulator"; @@ -67,6 +69,9 @@ const useStyles = makeStyles( option: { left: -theme.spacing(0.5), position: "relative" + }, + optionRadio: { + left: -theme.spacing(0.25) } }), { name: "FilterContent" } @@ -372,6 +377,38 @@ const FilterContent: React.FC = ({ } /> ))} + {filterField.type === FieldType.boolean && + filterField.options.map(option => ( +
+ + } + label={option.label} + name={filterField.name} + onChange={() => + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: [option.value] + } + }, + type: "set-property" + }) + } + /> +
+ ))}
)} diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index 3ed9beb05..eea2f96d5 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -2,6 +2,7 @@ import { FetchMoreProps } from "@saleor/types"; import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; export enum FieldType { + boolean, date, dateTime, number, diff --git a/src/utils/filters/fields.ts b/src/utils/filters/fields.ts index fb86a4a2f..69dae346b 100644 --- a/src/utils/filters/fields.ts +++ b/src/utils/filters/fields.ts @@ -79,3 +79,29 @@ export function createTextField( value: [defaultValue] }; } + +export function createBooleanField( + name: T, + label: string, + defaultValue: boolean, + labels: Record<"positive" | "negative", string> +): IFilterElement { + return { + active: false, + label, + multiple: false, + name, + options: [ + { + label: labels.positive, + value: true.toString() + }, + { + label: labels.negative, + value: false.toString() + } + ], + type: FieldType.boolean, + value: [defaultValue.toString()] + }; +} From d414729f01f1cc2ab9af23f40dba37ed32a44921 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 12:34:49 +0100 Subject: [PATCH 48/85] Add filtering to attributes --- .../AttributeListPage/AttributeListPage.tsx | 21 +- src/attributes/types.ts | 10 + src/attributes/urls.ts | 6 + .../views/AttributeList/AttributeList.tsx | 33 +-- src/attributes/views/AttributeList/filters.ts | 206 ++++++++++++++++++ .../views/AttributeList/messages.ts | 30 +++ 6 files changed, 287 insertions(+), 19 deletions(-) create mode 100644 src/attributes/types.ts create mode 100644 src/attributes/views/AttributeList/messages.ts diff --git a/src/attributes/components/AttributeListPage/AttributeListPage.tsx b/src/attributes/components/AttributeListPage/AttributeListPage.tsx index aadb1d933..57e2c97fa 100644 --- a/src/attributes/components/AttributeListPage/AttributeListPage.tsx +++ b/src/attributes/components/AttributeListPage/AttributeListPage.tsx @@ -4,25 +4,28 @@ import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import AppHeader from "@saleor/components/AppHeader"; -import SearchBar from "@saleor/components/SearchBar"; +import FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { AttributeListUrlSortField } from "@saleor/attributes/urls"; +import { AttributeFilterKeys } from "@saleor/attributes/views/AttributeList/filters"; +import { AttributeListFilterOpts } from "@saleor/attributes/types"; import Container from "../../../components/Container"; import PageHeader from "../../../components/PageHeader"; import { ListActions, PageListProps, - SearchPageProps, + FilterPageProps, TabPageProps, SortPage } from "../../../types"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList"; import AttributeList from "../AttributeList/AttributeList"; +import { createFilterStructure } from "../../views/AttributeList/filters"; export interface AttributeListPageProps extends PageListProps, ListActions, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { attributes: AttributeList_attributes_edges_node[]; @@ -30,9 +33,12 @@ export interface AttributeListPageProps } const AttributeListPage: React.FC = ({ + currencySymbol, + filterOpts, + initialSearch, onAdd, onBack, - initialSearch, + onFilterChange, onSearchChange, currentTab, onAll, @@ -44,6 +50,8 @@ const AttributeListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -58,18 +66,21 @@ const AttributeListPage: React.FC = ({ - ; + filterableInDashboard: FilterOpts; + filterableInStorefront: FilterOpts; + isVariantOnly: FilterOpts; + valueRequired: FilterOpts; + visibleInStorefront: FilterOpts; +} diff --git a/src/attributes/urls.ts b/src/attributes/urls.ts index fc0312068..9e498a6d9 100644 --- a/src/attributes/urls.ts +++ b/src/attributes/urls.ts @@ -15,6 +15,12 @@ import { export const attributeSection = "/attributes/"; export enum AttributeListUrlFiltersEnum { + availableInGrid = "availableInGrid", + filterableInDashboard = "filterableInDashboard", + filterableInStorefront = "filterableInStorefront", + isVariantOnly = "isVariantOnly", + valueRequired = "valueRequired", + visibleInStorefront = "visibleInStorefront", query = "query" } export type AttributeListUrlFilters = Filters; diff --git a/src/attributes/views/AttributeList/AttributeList.tsx b/src/attributes/views/AttributeList/AttributeList.tsx index 81e49d3f0..12f6207d5 100644 --- a/src/attributes/views/AttributeList/AttributeList.tsx +++ b/src/attributes/views/AttributeList/AttributeList.tsx @@ -9,7 +9,8 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + getFilterOpts } from "@saleor/attributes/views/AttributeList/filters"; import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; import SaveFilterTabDialog, { @@ -24,6 +25,7 @@ import usePaginator, { import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import { PAGINATE_BY } from "../../../config"; import useBulkActions from "../../../hooks/useBulkActions"; import { maybe } from "../../../misc"; @@ -35,12 +37,12 @@ import { AttributeBulkDelete } from "../../types/AttributeBulkDelete"; import { attributeAddUrl, attributeListUrl, - AttributeListUrlFilters, AttributeListUrlQueryParams, attributeUrl, AttributeListUrlDialog } from "../../urls"; import { getSortQueryVariables } from "./sort"; +import { getFilterQueryParam } from "./filters"; interface AttributeListProps { params: AttributeListUrlQueryParams; @@ -82,16 +84,17 @@ const AttributeList: React.FC = ({ params }) => { AttributeListUrlQueryParams >(navigate, attributeListUrl, params); - const changeFilterField = (filter: AttributeListUrlFilters) => { - reset(); - navigate( - attributeListUrl({ - ...getActiveFilters(params), - ...filter, - activeTab: undefined - }) - ); - }; + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + cleanupFn: reset, + createUrl: attributeListUrl, + getFilterQueryParam, + navigate, + params + }); const handleTabChange = (tab: number) => { reset(); @@ -146,15 +149,17 @@ const AttributeList: React.FC = ({ params }) => { )} currentTab={currentTab} disabled={loading || attributeBulkDeleteOpts.loading} + filterOpts={getFilterOpts(params)} initialSearch={params.query || ""} isChecked={isSelected} onAdd={() => navigate(attributeAddUrl())} - onAll={() => navigate(attributeListUrl())} + onAll={resetFilters} onBack={() => navigate(configurationMenuUrl)} + onFilterChange={changeFilters} onNextPage={loadNextPage} onPreviousPage={loadPreviousPage} onRowClick={id => () => navigate(attributeUrl(id))} - onSearchChange={query => changeFilterField({ query })} + onSearchChange={handleSearchChange} onSort={handleSort} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} diff --git a/src/attributes/views/AttributeList/filters.ts b/src/attributes/views/AttributeList/filters.ts index e6f2b9d1d..189d92262 100644 --- a/src/attributes/views/AttributeList/filters.ts +++ b/src/attributes/views/AttributeList/filters.ts @@ -1,4 +1,10 @@ +import { IntlShape } from "react-intl"; + import { AttributeFilterInput } from "@saleor/types/globalTypes"; +import { maybe, parseBoolean } from "@saleor/misc"; +import { createBooleanField } from "@saleor/utils/filters/fields"; +import { commonMessages } from "@saleor/intl"; +import { IFilter, IFilterElement } from "@saleor/components/Filter"; import { createFilterTabUtils, createFilterUtils @@ -8,17 +14,217 @@ import { AttributeListUrlFiltersEnum, AttributeListUrlQueryParams } from "../../urls"; +import { AttributeListFilterOpts } from "../../types"; +import messages from "./messages"; export const PRODUCT_FILTERS_KEY = "productFilters"; +export enum AttributeFilterKeys { + availableInGrid = "availableInGrud", + filterableInDashboard = "filterableInDashboard", + filterableInStorefront = "filterableInStorefront", + isVariantOnly = "isVariantOnly", + valueRequired = "valueRequired", + visibleInStorefront = "visibleInStorefront" +} + +export function getFilterOpts( + params: AttributeListUrlFilters +): AttributeListFilterOpts { + return { + availableInGrid: { + active: params.availableInGrid !== undefined, + value: maybe(() => parseBoolean(params.availableInGrid, true)) + }, + filterableInDashboard: { + active: params.filterableInDashboard !== undefined, + value: maybe(() => parseBoolean(params.filterableInDashboard, true)) + }, + filterableInStorefront: { + active: params.filterableInStorefront !== undefined, + value: maybe(() => parseBoolean(params.filterableInStorefront, true)) + }, + isVariantOnly: { + active: params.isVariantOnly !== undefined, + value: maybe(() => parseBoolean(params.isVariantOnly, true)) + }, + valueRequired: { + active: params.valueRequired !== undefined, + value: maybe(() => parseBoolean(params.valueRequired, true)) + }, + visibleInStorefront: { + active: params.visibleInStorefront !== undefined, + value: maybe(() => parseBoolean(params.visibleInStorefront, true)) + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: AttributeListFilterOpts +): IFilter { + return [ + { + ...createBooleanField( + AttributeFilterKeys.availableInGrid, + intl.formatMessage(messages.availableInGrid), + opts.availableInGrid.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.availableInGrid.active + }, + { + ...createBooleanField( + AttributeFilterKeys.filterableInDashboard, + intl.formatMessage(messages.filterableInDashboard), + opts.filterableInDashboard.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.filterableInDashboard.active + }, + { + ...createBooleanField( + AttributeFilterKeys.filterableInStorefront, + intl.formatMessage(messages.filterableInStorefront), + opts.filterableInStorefront.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.filterableInStorefront.active + }, + { + ...createBooleanField( + AttributeFilterKeys.isVariantOnly, + intl.formatMessage(messages.isVariantOnly), + opts.isVariantOnly.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.isVariantOnly.active + }, + { + ...createBooleanField( + AttributeFilterKeys.valueRequired, + intl.formatMessage(messages.valueRequired), + opts.valueRequired.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.valueRequired.active + }, + { + ...createBooleanField( + AttributeFilterKeys.visibleInStorefront, + intl.formatMessage(messages.visibleInStorefront), + opts.visibleInStorefront.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.visibleInStorefront.active + } + ]; +} + export function getFilterVariables( params: AttributeListUrlFilters ): AttributeFilterInput { return { + availableInGrid: + params.availableInGrid !== undefined + ? parseBoolean(params.availableInGrid, false) + : undefined, search: params.query }; } +export function getFilterQueryParam( + filter: IFilterElement +): AttributeListUrlFilters { + const { active, name, value } = filter; + + switch (name) { + case AttributeFilterKeys.availableInGrid: + if (!active) { + return { + availableInGrid: undefined + }; + } + + return { + availableInGrid: value[0] + }; + + case AttributeFilterKeys.filterableInDashboard: + if (!active) { + return { + filterableInDashboard: undefined + }; + } + + return { + filterableInDashboard: value[0] + }; + + case AttributeFilterKeys.filterableInStorefront: + if (!active) { + return { + filterableInStorefront: undefined + }; + } + + return { + filterableInStorefront: value[0] + }; + + case AttributeFilterKeys.isVariantOnly: + if (!active) { + return { + isVariantOnly: undefined + }; + } + + return { + isVariantOnly: value[0] + }; + + case AttributeFilterKeys.valueRequired: + if (!active) { + return { + valueRequired: undefined + }; + } + + return { + valueRequired: value[0] + }; + + case AttributeFilterKeys.visibleInStorefront: + if (!active) { + return { + visibleInStorefront: undefined + }; + } + + return { + visibleInStorefront: value[0] + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/attributes/views/AttributeList/messages.ts b/src/attributes/views/AttributeList/messages.ts new file mode 100644 index 000000000..be243a9a3 --- /dev/null +++ b/src/attributes/views/AttributeList/messages.ts @@ -0,0 +1,30 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + availableInGrid: { + defaultMessage: "Can be used as column", + description: "attribute can be column in product list table" + }, + filterableInDashboard: { + defaultMessage: "Filterable in Dashboard", + description: "use attribute in filtering" + }, + filterableInStorefront: { + defaultMessage: "Filterable in Storefront", + description: "use attribute in filtering" + }, + isVariantOnly: { + defaultMessage: "Variant Only", + description: "attribute can be used only in variants" + }, + valueRequired: { + defaultMessage: "Value Required", + description: "attribute value is required" + }, + visibleInStorefront: { + defaultMessage: "Visible on Product Page in Storefront", + description: "attribute" + } +}); + +export default messages; From 1e559b2feb603b2b11000ca1421bb0effa7de102 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 12:38:13 +0100 Subject: [PATCH 49/85] Fix types --- .../views/AttributeList/AttributeList.tsx | 4 +++ .../stories/attributes/AttributeListPage.tsx | 30 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/attributes/views/AttributeList/AttributeList.tsx b/src/attributes/views/AttributeList/AttributeList.tsx index 12f6207d5..63defa847 100644 --- a/src/attributes/views/AttributeList/AttributeList.tsx +++ b/src/attributes/views/AttributeList/AttributeList.tsx @@ -26,6 +26,7 @@ import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; +import useShop from "@saleor/hooks/useShop"; import { PAGINATE_BY } from "../../../config"; import useBulkActions from "../../../hooks/useBulkActions"; import { maybe } from "../../../misc"; @@ -52,6 +53,7 @@ const AttributeList: React.FC = ({ params }) => { const navigate = useNavigator(); const paginate = usePaginator(); const notify = useNotifier(); + const shop = useShop(); const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( params.ids ); @@ -138,6 +140,7 @@ const AttributeList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, attributeListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( @@ -147,6 +150,7 @@ const AttributeList: React.FC = ({ params }) => { attributes={maybe(() => data.attributes.edges.map(edge => edge.node) )} + currencySymbol={currencySymbol} currentTab={currentTab} disabled={loading || attributeBulkDeleteOpts.loading} filterOpts={getFilterOpts(params)} diff --git a/src/storybook/stories/attributes/AttributeListPage.tsx b/src/storybook/stories/attributes/AttributeListPage.tsx index 020aa6e9e..61bfc3038 100644 --- a/src/storybook/stories/attributes/AttributeListPage.tsx +++ b/src/storybook/stories/attributes/AttributeListPage.tsx @@ -10,7 +10,8 @@ import { pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "@saleor/fixtures"; import { AttributeListUrlSortField } from "@saleor/attributes/urls"; import Decorator from "../../Decorator"; @@ -20,7 +21,34 @@ const props: AttributeListPageProps = { ...listActionsProps, ...tabPageProps, ...searchPageProps, + ...filterPageProps, attributes, + filterOpts: { + availableInGrid: { + active: false, + value: false + }, + filterableInDashboard: { + active: false, + value: false + }, + filterableInStorefront: { + active: false, + value: false + }, + isVariantOnly: { + active: false, + value: false + }, + valueRequired: { + active: false, + value: false + }, + visibleInStorefront: { + active: false, + value: false + } + }, onBack: () => undefined, onSort: () => undefined, sort: { From bd84bcbbf0f973fc6b596ef05ed2fb419ba82502 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 12:39:52 +0100 Subject: [PATCH 50/85] Update snapshots --- .../__snapshots__/Stories.test.ts.snap | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index f9b7c283f..12e4a7e72 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -12211,8 +12211,21 @@ exports[`Storyshots Views / Attributes / Attribute list default 1`] = `
+
+ +
@@ -13187,8 +13200,21 @@ exports[`Storyshots Views / Attributes / Attribute list loading 1`] = `
+
+ +
@@ -13579,8 +13605,21 @@ exports[`Storyshots Views / Attributes / Attribute list no data 1`] = `
+
+ +
From 12255d7f386afebffba312e43fcaf1cbaa5c8800 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 15:12:10 +0100 Subject: [PATCH 51/85] Add missing filters to query variables --- src/attributes/views/AttributeList/filters.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/attributes/views/AttributeList/filters.ts b/src/attributes/views/AttributeList/filters.ts index 189d92262..48a2772b1 100644 --- a/src/attributes/views/AttributeList/filters.ts +++ b/src/attributes/views/AttributeList/filters.ts @@ -147,7 +147,27 @@ export function getFilterVariables( params.availableInGrid !== undefined ? parseBoolean(params.availableInGrid, false) : undefined, - search: params.query + filterableInDashboard: + params.filterableInDashboard !== undefined + ? parseBoolean(params.filterableInDashboard, false) + : undefined, + filterableInStorefront: + params.filterableInStorefront !== undefined + ? parseBoolean(params.filterableInStorefront, false) + : undefined, + isVariantOnly: + params.isVariantOnly !== undefined + ? parseBoolean(params.isVariantOnly, false) + : undefined, + search: params.query, + valueRequired: + params.valueRequired !== undefined + ? parseBoolean(params.valueRequired, false) + : undefined, + visibleInStorefront: + params.visibleInStorefront !== undefined + ? parseBoolean(params.visibleInStorefront, false) + : undefined }; } From c90c58473fc450d9af1437ecdd6df0bf87490b58 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 15:27:44 +0100 Subject: [PATCH 52/85] Move filter structure to page component --- .../AttributeListPage/AttributeListPage.tsx | 8 +- .../components/AttributeListPage/filters.ts | 131 ++++++++++++++++++ .../components/AttributeListPage/index.ts | 1 + src/attributes/types.ts | 10 -- src/attributes/views/AttributeList/filters.ts | 101 +------------- .../views/AttributeList/messages.ts | 30 ---- 6 files changed, 142 insertions(+), 139 deletions(-) create mode 100644 src/attributes/components/AttributeListPage/filters.ts delete mode 100644 src/attributes/types.ts delete mode 100644 src/attributes/views/AttributeList/messages.ts diff --git a/src/attributes/components/AttributeListPage/AttributeListPage.tsx b/src/attributes/components/AttributeListPage/AttributeListPage.tsx index 57e2c97fa..70f8733c2 100644 --- a/src/attributes/components/AttributeListPage/AttributeListPage.tsx +++ b/src/attributes/components/AttributeListPage/AttributeListPage.tsx @@ -7,8 +7,6 @@ import AppHeader from "@saleor/components/AppHeader"; import FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { AttributeListUrlSortField } from "@saleor/attributes/urls"; -import { AttributeFilterKeys } from "@saleor/attributes/views/AttributeList/filters"; -import { AttributeListFilterOpts } from "@saleor/attributes/types"; import Container from "../../../components/Container"; import PageHeader from "../../../components/PageHeader"; import { @@ -20,7 +18,11 @@ import { } from "../../../types"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList"; import AttributeList from "../AttributeList/AttributeList"; -import { createFilterStructure } from "../../views/AttributeList/filters"; +import { + createFilterStructure, + AttributeListFilterOpts, + AttributeFilterKeys +} from "./filters"; export interface AttributeListPageProps extends PageListProps, diff --git a/src/attributes/components/AttributeListPage/filters.ts b/src/attributes/components/AttributeListPage/filters.ts new file mode 100644 index 000000000..30acfd23a --- /dev/null +++ b/src/attributes/components/AttributeListPage/filters.ts @@ -0,0 +1,131 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { FilterOpts } from "@saleor/types"; +import { commonMessages } from "@saleor/intl"; +import { IFilter } from "@saleor/components/Filter"; +import { createBooleanField } from "@saleor/utils/filters/fields"; + +export enum AttributeFilterKeys { + availableInGrid = "availableInGrud", + filterableInDashboard = "filterableInDashboard", + filterableInStorefront = "filterableInStorefront", + isVariantOnly = "isVariantOnly", + valueRequired = "valueRequired", + visibleInStorefront = "visibleInStorefront" +} + +export interface AttributeListFilterOpts { + availableInGrid: FilterOpts; + filterableInDashboard: FilterOpts; + filterableInStorefront: FilterOpts; + isVariantOnly: FilterOpts; + valueRequired: FilterOpts; + visibleInStorefront: FilterOpts; +} + +const messages = defineMessages({ + availableInGrid: { + defaultMessage: "Can be used as column", + description: "attribute can be column in product list table" + }, + filterableInDashboard: { + defaultMessage: "Filterable in Dashboard", + description: "use attribute in filtering" + }, + filterableInStorefront: { + defaultMessage: "Filterable in Storefront", + description: "use attribute in filtering" + }, + isVariantOnly: { + defaultMessage: "Variant Only", + description: "attribute can be used only in variants" + }, + valueRequired: { + defaultMessage: "Value Required", + description: "attribute value is required" + }, + visibleInStorefront: { + defaultMessage: "Visible on Product Page in Storefront", + description: "attribute" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: AttributeListFilterOpts +): IFilter { + return [ + { + ...createBooleanField( + AttributeFilterKeys.availableInGrid, + intl.formatMessage(messages.availableInGrid), + opts.availableInGrid.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.availableInGrid.active + }, + { + ...createBooleanField( + AttributeFilterKeys.filterableInDashboard, + intl.formatMessage(messages.filterableInDashboard), + opts.filterableInDashboard.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.filterableInDashboard.active + }, + { + ...createBooleanField( + AttributeFilterKeys.filterableInStorefront, + intl.formatMessage(messages.filterableInStorefront), + opts.filterableInStorefront.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.filterableInStorefront.active + }, + { + ...createBooleanField( + AttributeFilterKeys.isVariantOnly, + intl.formatMessage(messages.isVariantOnly), + opts.isVariantOnly.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.isVariantOnly.active + }, + { + ...createBooleanField( + AttributeFilterKeys.valueRequired, + intl.formatMessage(messages.valueRequired), + opts.valueRequired.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.valueRequired.active + }, + { + ...createBooleanField( + AttributeFilterKeys.visibleInStorefront, + intl.formatMessage(messages.visibleInStorefront), + opts.visibleInStorefront.value, + { + negative: intl.formatMessage(commonMessages.no), + positive: intl.formatMessage(commonMessages.yes) + } + ), + active: opts.visibleInStorefront.active + } + ]; +} diff --git a/src/attributes/components/AttributeListPage/index.ts b/src/attributes/components/AttributeListPage/index.ts index 2633c7769..dea1bb374 100644 --- a/src/attributes/components/AttributeListPage/index.ts +++ b/src/attributes/components/AttributeListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./AttributeListPage"; export * from "./AttributeListPage"; +export * from "./filters"; diff --git a/src/attributes/types.ts b/src/attributes/types.ts deleted file mode 100644 index 2ac48786c..000000000 --- a/src/attributes/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FilterOpts } from "@saleor/types"; - -export interface AttributeListFilterOpts { - availableInGrid: FilterOpts; - filterableInDashboard: FilterOpts; - filterableInStorefront: FilterOpts; - isVariantOnly: FilterOpts; - valueRequired: FilterOpts; - visibleInStorefront: FilterOpts; -} diff --git a/src/attributes/views/AttributeList/filters.ts b/src/attributes/views/AttributeList/filters.ts index 48a2772b1..96c1ac929 100644 --- a/src/attributes/views/AttributeList/filters.ts +++ b/src/attributes/views/AttributeList/filters.ts @@ -1,10 +1,10 @@ -import { IntlShape } from "react-intl"; - import { AttributeFilterInput } from "@saleor/types/globalTypes"; import { maybe, parseBoolean } from "@saleor/misc"; -import { createBooleanField } from "@saleor/utils/filters/fields"; -import { commonMessages } from "@saleor/intl"; -import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { IFilterElement } from "@saleor/components/Filter"; +import { + AttributeListFilterOpts, + AttributeFilterKeys +} from "@saleor/attributes/components/AttributeListPage"; import { createFilterTabUtils, createFilterUtils @@ -14,20 +14,9 @@ import { AttributeListUrlFiltersEnum, AttributeListUrlQueryParams } from "../../urls"; -import { AttributeListFilterOpts } from "../../types"; -import messages from "./messages"; export const PRODUCT_FILTERS_KEY = "productFilters"; -export enum AttributeFilterKeys { - availableInGrid = "availableInGrud", - filterableInDashboard = "filterableInDashboard", - filterableInStorefront = "filterableInStorefront", - isVariantOnly = "isVariantOnly", - valueRequired = "valueRequired", - visibleInStorefront = "visibleInStorefront" -} - export function getFilterOpts( params: AttributeListUrlFilters ): AttributeListFilterOpts { @@ -59,86 +48,6 @@ export function getFilterOpts( }; } -export function createFilterStructure( - intl: IntlShape, - opts: AttributeListFilterOpts -): IFilter { - return [ - { - ...createBooleanField( - AttributeFilterKeys.availableInGrid, - intl.formatMessage(messages.availableInGrid), - opts.availableInGrid.value, - { - negative: intl.formatMessage(commonMessages.no), - positive: intl.formatMessage(commonMessages.yes) - } - ), - active: opts.availableInGrid.active - }, - { - ...createBooleanField( - AttributeFilterKeys.filterableInDashboard, - intl.formatMessage(messages.filterableInDashboard), - opts.filterableInDashboard.value, - { - negative: intl.formatMessage(commonMessages.no), - positive: intl.formatMessage(commonMessages.yes) - } - ), - active: opts.filterableInDashboard.active - }, - { - ...createBooleanField( - AttributeFilterKeys.filterableInStorefront, - intl.formatMessage(messages.filterableInStorefront), - opts.filterableInStorefront.value, - { - negative: intl.formatMessage(commonMessages.no), - positive: intl.formatMessage(commonMessages.yes) - } - ), - active: opts.filterableInStorefront.active - }, - { - ...createBooleanField( - AttributeFilterKeys.isVariantOnly, - intl.formatMessage(messages.isVariantOnly), - opts.isVariantOnly.value, - { - negative: intl.formatMessage(commonMessages.no), - positive: intl.formatMessage(commonMessages.yes) - } - ), - active: opts.isVariantOnly.active - }, - { - ...createBooleanField( - AttributeFilterKeys.valueRequired, - intl.formatMessage(messages.valueRequired), - opts.valueRequired.value, - { - negative: intl.formatMessage(commonMessages.no), - positive: intl.formatMessage(commonMessages.yes) - } - ), - active: opts.valueRequired.active - }, - { - ...createBooleanField( - AttributeFilterKeys.visibleInStorefront, - intl.formatMessage(messages.visibleInStorefront), - opts.visibleInStorefront.value, - { - negative: intl.formatMessage(commonMessages.no), - positive: intl.formatMessage(commonMessages.yes) - } - ), - active: opts.visibleInStorefront.active - } - ]; -} - export function getFilterVariables( params: AttributeListUrlFilters ): AttributeFilterInput { diff --git a/src/attributes/views/AttributeList/messages.ts b/src/attributes/views/AttributeList/messages.ts deleted file mode 100644 index be243a9a3..000000000 --- a/src/attributes/views/AttributeList/messages.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { defineMessages } from "react-intl"; - -const messages = defineMessages({ - availableInGrid: { - defaultMessage: "Can be used as column", - description: "attribute can be column in product list table" - }, - filterableInDashboard: { - defaultMessage: "Filterable in Dashboard", - description: "use attribute in filtering" - }, - filterableInStorefront: { - defaultMessage: "Filterable in Storefront", - description: "use attribute in filtering" - }, - isVariantOnly: { - defaultMessage: "Variant Only", - description: "attribute can be used only in variants" - }, - valueRequired: { - defaultMessage: "Value Required", - description: "attribute value is required" - }, - visibleInStorefront: { - defaultMessage: "Visible on Product Page in Storefront", - description: "attribute" - } -}); - -export default messages; From a4a9703abd327d57a5ede65ab3afd0266148f081 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 16:05:32 +0100 Subject: [PATCH 53/85] Add filters to service list --- src/orders/views/OrderList/OrderList.tsx | 2 +- .../ServiceListPage/ServiceListPage.tsx | 23 ++++-- .../components/ServiceListPage/filters.ts | 47 ++++++++++++ src/services/urls.ts | 1 + .../views/ServiceList/ServiceList.tsx | 36 ++++++---- src/services/views/ServiceList/filter.ts | 31 -------- src/services/views/ServiceList/filters.ts | 71 +++++++++++++++++++ 7 files changed, 161 insertions(+), 50 deletions(-) create mode 100644 src/services/components/ServiceListPage/filters.ts delete mode 100644 src/services/views/ServiceList/filter.ts create mode 100644 src/services/views/ServiceList/filters.ts diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index f078616e9..c17d82d6c 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -209,7 +209,7 @@ export const OrderList: React.FC = ({ params }) => { } onSearchChange={handleSearchChange} - onFilterChange={filter => changeFilters(filter)} + onFilterChange={changeFilters} onTabSave={() => openModal("save-search")} onTabDelete={() => openModal("delete-search")} onTabChange={handleTabChange} diff --git a/src/services/components/ServiceListPage/ServiceListPage.tsx b/src/services/components/ServiceListPage/ServiceListPage.tsx index 93ee114bf..e3c6c2dc7 100644 --- a/src/services/components/ServiceListPage/ServiceListPage.tsx +++ b/src/services/components/ServiceListPage/ServiceListPage.tsx @@ -6,21 +6,26 @@ 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 FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { PageListProps, - SearchPageProps, TabPageProps, - SortPage + SortPage, + FilterPageProps } from "@saleor/types"; import { ServiceListUrlSortField } from "@saleor/services/urls"; import { ServiceList_serviceAccounts_edges_node } from "../../types/ServiceList"; import ServiceList from "../ServiceList"; +import { + ServiceFilterKeys, + ServiceListFilterOpts, + createFilterStructure +} from "./filters"; export interface ServiceListPageProps extends PageListProps, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { services: ServiceList_serviceAccounts_edges_node[]; @@ -29,11 +34,14 @@ export interface ServiceListPageProps } const ServiceListPage: React.FC = ({ + currencySymbol, currentTab, + filterOpts, initialSearch, onAdd, onAll, onBack, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -43,6 +51,8 @@ const ServiceListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -57,11 +67,13 @@ const ServiceListPage: React.FC = ({ - = ({ })} tabs={tabs} onAll={onAll} + onFilterChange={onFilterChange} onSearchChange={onSearchChange} onTabChange={onTabChange} onTabDelete={onTabDelete} diff --git a/src/services/components/ServiceListPage/filters.ts b/src/services/components/ServiceListPage/filters.ts new file mode 100644 index 000000000..cf864cf54 --- /dev/null +++ b/src/services/components/ServiceListPage/filters.ts @@ -0,0 +1,47 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { FilterOpts } from "@saleor/types"; +import { IFilter } from "@saleor/components/Filter"; +import { createBooleanField } from "@saleor/utils/filters/fields"; +import { commonMessages } from "@saleor/intl"; + +export enum ServiceFilterKeys { + active = "active" +} + +export interface ServiceListFilterOpts { + isActive: FilterOpts; +} + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "service account" + }, + deactivated: { + defaultMessage: "Inactive", + description: "service account" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: ServiceListFilterOpts +): IFilter { + return [ + { + ...createBooleanField( + ServiceFilterKeys.active, + intl.formatMessage(commonMessages.status), + opts.isActive.value, + { + negative: intl.formatMessage(messages.deactivated), + positive: intl.formatMessage(messages.active) + } + ), + active: opts.isActive.active + } + ]; +} + +export default messages; diff --git a/src/services/urls.ts b/src/services/urls.ts index d14e8e2f0..4f5a29d1d 100644 --- a/src/services/urls.ts +++ b/src/services/urls.ts @@ -15,6 +15,7 @@ export const serviceSection = "/services/"; export const serviceListPath = serviceSection; export enum ServiceListUrlFiltersEnum { + active = "active", query = "query" } export type ServiceListUrlFilters = Filters; diff --git a/src/services/views/ServiceList/ServiceList.tsx b/src/services/views/ServiceList/ServiceList.tsx index fe29a1826..6c06fde1b 100644 --- a/src/services/views/ServiceList/ServiceList.tsx +++ b/src/services/views/ServiceList/ServiceList.tsx @@ -20,6 +20,8 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import useShop from "@saleor/hooks/useShop"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import ServiceDeleteDialog from "../../components/ServiceDeleteDialog"; import ServiceListPage from "../../components/ServiceListPage"; import { useServiceListQuery } from "../../queries"; @@ -27,7 +29,6 @@ import { serviceAddUrl, serviceListUrl, ServiceListUrlDialog, - ServiceListUrlFilters, ServiceListUrlQueryParams, serviceUrl } from "../../urls"; @@ -36,9 +37,11 @@ import { deleteFilterTab, getActiveFilters, getFilterTabs, + getFilterQueryParam, getFilterVariables, - saveFilterTab -} from "./filter"; + saveFilterTab, + getFilterOpts +} from "./filters"; import { getSortQueryVariables } from "./sort"; interface ServiceListProps { @@ -49,6 +52,7 @@ export const ServiceList: React.FC = ({ params }) => { const navigate = useNavigator(); const notify = useNotifier(); const paginate = usePaginator(); + const shop = useShop(); const { updateListSettings, settings } = useListSettings( ListViews.STAFF_MEMBERS_LIST ); @@ -77,14 +81,16 @@ export const ServiceList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: ServiceListUrlFilters) => - navigate( - serviceListUrl({ - ...getActiveFilters(params), - ...filter, - activeTab: undefined - }) - ); + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + createUrl: serviceListUrl, + getFilterQueryParam, + navigate, + params + }); const [openModal, closeModal] = createDialogActionHandlers< ServiceListUrlDialog, @@ -129,6 +135,7 @@ export const ServiceList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, serviceListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( @@ -143,10 +150,13 @@ export const ServiceList: React.FC = ({ params }) => { return ( <> changeFilterField({ query })} - onAll={() => navigate(serviceListUrl())} + onSearchChange={handleSearchChange} + onFilterChange={changeFilters} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/services/views/ServiceList/filter.ts b/src/services/views/ServiceList/filter.ts deleted file mode 100644 index a1d2b7724..000000000 --- a/src/services/views/ServiceList/filter.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ServiceAccountFilterInput } from "@saleor/types/globalTypes"; -import { - createFilterTabUtils, - createFilterUtils -} from "../../../utils/filters"; -import { - ServiceListUrlFilters, - ServiceListUrlFiltersEnum, - ServiceListUrlQueryParams -} from "../../urls"; - -export const STAFF_FILTERS_KEY = "staffFilters"; - -export function getFilterVariables( - params: ServiceListUrlFilters -): ServiceAccountFilterInput { - return { - search: params.query - }; -} - -export const { - deleteFilterTab, - getFilterTabs, - saveFilterTab -} = createFilterTabUtils(STAFF_FILTERS_KEY); - -export const { areFiltersApplied, getActiveFilters } = createFilterUtils< - ServiceListUrlQueryParams, - ServiceListUrlFilters ->(ServiceListUrlFiltersEnum); diff --git a/src/services/views/ServiceList/filters.ts b/src/services/views/ServiceList/filters.ts new file mode 100644 index 000000000..36fe240a2 --- /dev/null +++ b/src/services/views/ServiceList/filters.ts @@ -0,0 +1,71 @@ +import { ServiceAccountFilterInput } from "@saleor/types/globalTypes"; +import { + ServiceListFilterOpts, + ServiceFilterKeys +} from "@saleor/services/components/ServiceListPage/filters"; +import { maybe, parseBoolean } from "@saleor/misc"; +import { IFilterElement } from "@saleor/components/Filter"; +import { + ServiceListUrlFilters, + ServiceListUrlFiltersEnum, + ServiceListUrlQueryParams +} from "../../urls"; +import { + createFilterTabUtils, + createFilterUtils +} from "../../../utils/filters"; + +export const STAFF_FILTERS_KEY = "staffFilters"; + +export function getFilterOpts( + params: ServiceListUrlFilters +): ServiceListFilterOpts { + return { + isActive: { + active: maybe(() => params.active !== undefined, false), + value: + params.active !== undefined ? parseBoolean(params.active, true) : true + } + }; +} + +export function getFilterVariables( + params: ServiceListUrlFilters +): ServiceAccountFilterInput { + return { + isActive: + params.active !== undefined + ? parseBoolean(params.active, true) + : undefined, + search: params.query + }; +} + +export function getFilterQueryParam( + filter: IFilterElement +): ServiceListUrlFilters { + const { active, name, value } = filter; + + switch (name) { + case ServiceFilterKeys.active: + if (!active) { + return { + active: undefined + }; + } + return { + active: value[0] + }; + } +} + +export const { + deleteFilterTab, + getFilterTabs, + saveFilterTab +} = createFilterTabUtils(STAFF_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + ServiceListUrlQueryParams, + ServiceListUrlFilters +>(ServiceListUrlFiltersEnum); From c5b3e22ec58f6645c5ee504c30ff6c96e97857c7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 16:08:24 +0100 Subject: [PATCH 54/85] Update stories --- .../ServiceListPage.stories.tsx | 10 ++++- .../__snapshots__/Stories.test.ts.snap | 45 +++++++++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/services/components/ServiceListPage/ServiceListPage.stories.tsx b/src/services/components/ServiceListPage/ServiceListPage.stories.tsx index cfa666b1e..d3f5d74e1 100644 --- a/src/services/components/ServiceListPage/ServiceListPage.stories.tsx +++ b/src/services/components/ServiceListPage/ServiceListPage.stories.tsx @@ -6,7 +6,8 @@ import { pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "@saleor/fixtures"; import ServiceListPage, { ServiceListPageProps @@ -21,6 +22,13 @@ const props: ServiceListPageProps = { ...searchPageProps, ...sortPageProps, ...tabPageProps, + ...filterPageProps, + filterOpts: { + isActive: { + active: false, + value: true + } + }, onBack: () => undefined, onRemove: () => undefined, services: serviceList, diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 12e4a7e72..6583d0ca9 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -111719,8 +111719,21 @@ exports[`Storyshots Views / Services / Service list default 1`] = `
+
+ +
@@ -112198,8 +112211,21 @@ exports[`Storyshots Views / Services / Service list loading 1`] = `
+
+ +
@@ -112550,8 +112576,21 @@ exports[`Storyshots Views / Services / Service list no data 1`] = `
+
+ +
From 3beb283ef662629ec81c2c1ee82d2d0aaaf9c3e9 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 16:38:33 +0100 Subject: [PATCH 55/85] Add filtering to webhooks --- .../WebhooksListPage/WebhooksListPage.tsx | 24 +++++-- .../components/WebhooksListPage/filters.ts | 45 +++++++++++++ .../components/WebhooksListPage/index.ts | 1 + src/webhooks/urls.ts | 1 + .../views/WebhookList/WebhookList.tsx | 58 ++++++++-------- src/webhooks/views/WebhookList/filter.ts | 28 -------- src/webhooks/views/WebhookList/filters.ts | 67 +++++++++++++++++++ 7 files changed, 161 insertions(+), 63 deletions(-) create mode 100644 src/webhooks/components/WebhooksListPage/filters.ts delete mode 100644 src/webhooks/views/WebhookList/filter.ts create mode 100644 src/webhooks/views/WebhookList/filters.ts diff --git a/src/webhooks/components/WebhooksListPage/WebhooksListPage.tsx b/src/webhooks/components/WebhooksListPage/WebhooksListPage.tsx index 523b91b38..53ad7f01e 100644 --- a/src/webhooks/components/WebhooksListPage/WebhooksListPage.tsx +++ b/src/webhooks/components/WebhooksListPage/WebhooksListPage.tsx @@ -6,21 +6,26 @@ 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 FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { PageListProps, - SearchPageProps, + FilterPageProps, TabPageProps, SortPage } from "@saleor/types"; import { WebhookListUrlSortField } from "@saleor/webhooks/urls"; import { Webhooks_webhooks_edges_node } from "../../types/Webhooks"; -import WebhooksList from "../WebhooksList/WebhooksList"; +import WebhooksList from "../WebhooksList"; +import { + WebhookFilterKeys, + WebhookListFilterOpts, + createFilterStructure +} from "./filters"; export interface WebhooksListPageProps extends PageListProps, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { webhooks: Webhooks_webhooks_edges_node[]; @@ -29,11 +34,14 @@ export interface WebhooksListPageProps } const WebhooksListPage: React.FC = ({ + currencySymbol, currentTab, + filterOpts, initialSearch, onAdd, onAll, onBack, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -43,6 +51,9 @@ const WebhooksListPage: React.FC = ({ ...listProps }) => { const intl = useIntl(); + + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -57,18 +68,21 @@ const WebhooksListPage: React.FC = ({ - ; +} + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "webhook" + }, + inactive: { + defaultMessage: "Inactive", + description: "webhook" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: WebhookListFilterOpts +): IFilter { + return [ + { + ...createBooleanField( + WebhookFilterKeys.isActive, + intl.formatMessage(commonMessages.status), + opts.isActive.value, + { + negative: intl.formatMessage(messages.inactive), + positive: intl.formatMessage(messages.active) + } + ), + active: opts.isActive.active + } + ]; +} diff --git a/src/webhooks/components/WebhooksListPage/index.ts b/src/webhooks/components/WebhooksListPage/index.ts index 68f52d520..3c0f1c803 100644 --- a/src/webhooks/components/WebhooksListPage/index.ts +++ b/src/webhooks/components/WebhooksListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./WebhooksListPage"; export * from "./WebhooksListPage"; +export * from "./filters"; diff --git a/src/webhooks/urls.ts b/src/webhooks/urls.ts index 6a7de1b38..b01c39865 100644 --- a/src/webhooks/urls.ts +++ b/src/webhooks/urls.ts @@ -15,6 +15,7 @@ export const webhookSection = "/webhooks/"; export const webhookListPath = webhookSection; export enum WebhookListUrlFiltersEnum { + active = "active", query = "query" } export type WebhookListUrlFilters = Filters; diff --git a/src/webhooks/views/WebhookList/WebhookList.tsx b/src/webhooks/views/WebhookList/WebhookList.tsx index 396129a2d..20de84347 100644 --- a/src/webhooks/views/WebhookList/WebhookList.tsx +++ b/src/webhooks/views/WebhookList/WebhookList.tsx @@ -19,12 +19,14 @@ import { useIntl } from "react-intl"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import useShop from "@saleor/hooks/useShop"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import WebhooksListPage from "../../components/WebhooksListPage/WebhooksListPage"; import { TypedWebhookDelete } from "../../mutations"; import { useWebhooksListQuery } from "../../queries"; import { WebhookListUrlDialog, - WebhookListUrlFilters, webhookAddUrl, webhookListUrl, WebhookListUrlQueryParams, @@ -37,8 +39,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab -} from "./filter"; + getFilterQueryParam, + saveFilterTab, + getFilterOpts +} from "./filters"; interface WebhooksListProps { params: WebhookListUrlQueryParams; @@ -49,6 +53,7 @@ export const WebhooksList: React.FC = ({ params }) => { const paginate = usePaginator(); const notify = useNotifier(); const intl = useIntl(); + const shop = useShop(); const { updateListSettings, settings } = useListSettings( ListViews.WEBHOOK_LIST ); @@ -76,32 +81,21 @@ export const WebhooksList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: WebhookListUrlFilters) => - navigate( - webhookListUrl({ - ...getActiveFilters(params), - ...filter, - activeTab: undefined - }) - ); - const closeModal = () => - navigate( - webhookListUrl({ - ...params, - action: undefined, - id: undefined - }), - true - ); + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + createUrl: webhookListUrl, + getFilterQueryParam, + navigate, + params + }); - const openModal = (action: WebhookListUrlDialog, id?: string) => - navigate( - webhookListUrl({ - ...params, - action, - id - }) - ); + const [openModal, closeModal] = createDialogActionHandlers< + WebhookListUrlDialog, + WebhookListUrlQueryParams + >(navigate, webhookListUrl, params); const handleTabChange = (tab: number) => { navigate( @@ -133,6 +127,7 @@ export const WebhooksList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, webhookListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( @@ -162,10 +157,13 @@ export const WebhooksList: React.FC = ({ params }) => { return ( <> changeFilterField({ query })} - onAll={() => navigate(webhookListUrl())} + onFilterChange={changeFilters} + onSearchChange={handleSearchChange} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/webhooks/views/WebhookList/filter.ts b/src/webhooks/views/WebhookList/filter.ts deleted file mode 100644 index c5445c621..000000000 --- a/src/webhooks/views/WebhookList/filter.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { WebhookFilterInput } from "@saleor/types/globalTypes"; -import { createFilterTabUtils, createFilterUtils } from "@saleor/utils/filters"; -import { - WebhookListUrlFilters, - WebhookListUrlFiltersEnum, - WebhookListUrlQueryParams -} from "../../urls"; - -export const WEBHOOK_FILTERS_KEY = "webhookFilters"; - -export function getFilterVariables( - params: WebhookListUrlFilters -): WebhookFilterInput { - return { - search: params.query - }; -} - -export const { - deleteFilterTab, - getFilterTabs, - saveFilterTab -} = createFilterTabUtils(WEBHOOK_FILTERS_KEY); - -export const { areFiltersApplied, getActiveFilters } = createFilterUtils< - WebhookListUrlQueryParams, - WebhookListUrlFilters ->(WebhookListUrlFiltersEnum); diff --git a/src/webhooks/views/WebhookList/filters.ts b/src/webhooks/views/WebhookList/filters.ts new file mode 100644 index 000000000..6dff2e242 --- /dev/null +++ b/src/webhooks/views/WebhookList/filters.ts @@ -0,0 +1,67 @@ +import { WebhookFilterInput } from "@saleor/types/globalTypes"; +import { createFilterTabUtils, createFilterUtils } from "@saleor/utils/filters"; +import { IFilterElement } from "@saleor/components/Filter"; +import { + WebhookListFilterOpts, + WebhookFilterKeys +} from "@saleor/webhooks/components/WebhooksListPage"; +import { parseBoolean, maybe } from "@saleor/misc"; +import { + WebhookListUrlFilters, + WebhookListUrlFiltersEnum, + WebhookListUrlQueryParams +} from "../../urls"; + +export const WEBHOOK_FILTERS_KEY = "webhookFilters"; + +export function getFilterOpts( + params: WebhookListUrlFilters +): WebhookListFilterOpts { + return { + isActive: { + active: maybe(() => params.active !== undefined, false), + value: + params.active !== undefined ? parseBoolean(params.active, true) : true + } + }; +} + +export function getFilterVariables( + params: WebhookListUrlFilters +): WebhookFilterInput { + return { + isActive: params.active + ? parseBoolean(params.active, undefined) + : undefined, + search: params.query + }; +} + +export function getFilterQueryParam( + filter: IFilterElement +): WebhookListUrlFilters { + const { active, name, value } = filter; + + switch (name) { + case WebhookFilterKeys.isActive: + if (!active) { + return { + active: undefined + }; + } + return { + active: value[0] + }; + } +} + +export const { + deleteFilterTab, + getFilterTabs, + saveFilterTab +} = createFilterTabUtils(WEBHOOK_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + WebhookListUrlQueryParams, + WebhookListUrlFilters +>(WebhookListUrlFiltersEnum); From 36501a7131850ff9fba1caa6c43b11de57fd3f64 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 16:42:29 +0100 Subject: [PATCH 56/85] Update stories --- .../__snapshots__/Stories.test.ts.snap | 45 +++++++++++++++++-- .../WebhookListPage.stories.tsx | 10 ++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 6583d0ca9..4cfd36ed6 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -127604,8 +127604,21 @@ exports[`Storyshots Views / Webhooks / Webhook list default 1`] = `
+
+ +
@@ -128024,8 +128037,21 @@ exports[`Storyshots Views / Webhooks / Webhook list loading 1`] = `
+
+ +
@@ -128393,8 +128419,21 @@ exports[`Storyshots Views / Webhooks / Webhook list no data 1`] = `
+
+ +
diff --git a/src/webhooks/components/WebhooksListPage/WebhookListPage.stories.tsx b/src/webhooks/components/WebhooksListPage/WebhookListPage.stories.tsx index 4c9314949..077ed6ddf 100644 --- a/src/webhooks/components/WebhooksListPage/WebhookListPage.stories.tsx +++ b/src/webhooks/components/WebhooksListPage/WebhookListPage.stories.tsx @@ -6,7 +6,8 @@ import { pageListProps, searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "@saleor/fixtures"; import Decorator from "@saleor/storybook/Decorator"; import { WebhookListUrlSortField } from "@saleor/webhooks/urls"; @@ -19,6 +20,13 @@ const props: WebhooksListPageProps = { ...searchPageProps, ...sortPageProps, ...tabPageProps, + ...filterPageProps, + filterOpts: { + isActive: { + active: false, + value: true + } + }, onBack: () => undefined, onRemove: () => undefined, sort: { From e4397b1f75ca7af3afeeb962b479a41c6726abd7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 17:18:59 +0100 Subject: [PATCH 57/85] Fix sorting input typing --- src/utils/sort.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/utils/sort.ts b/src/utils/sort.ts index 6288e8191..0a12f26af 100644 --- a/src/utils/sort.ts +++ b/src/utils/sort.ts @@ -79,8 +79,16 @@ export function createGetSortQueryVariables< >( getSortQueryField: GetSortQueryField ): GetSortQueryVariables { - return (params: TParams) => ({ - direction: getOrderDirection(params.asc), - field: getSortQueryField(params.sort) - }); + return (params: TParams) => { + const field = getSortQueryField(params.sort); + + if (!!field) { + return { + direction: getOrderDirection(params.asc), + field + }; + } + + return undefined; + }; } From 35324951ecbeb15dfcfc06125f1bb4478004e38f Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 17:30:42 +0100 Subject: [PATCH 58/85] Add tabs and filters to plugins --- .../components/PluginsList/PluginsList.tsx | 195 +++++++++--------- .../PluginsListPage/PluginsListPage.tsx | 56 ++++- .../components/PluginsListPage/filters.ts | 45 ++++ .../components/PluginsListPage/index.ts | 1 + src/plugins/queries.ts | 2 + src/plugins/types/Plugins.ts | 3 +- src/plugins/urls.ts | 21 +- src/plugins/views/PluginList/PluginList.tsx | 95 ++++++++- src/plugins/views/PluginList/filters.ts | 71 +++++++ src/types/globalTypes.ts | 5 + 10 files changed, 388 insertions(+), 106 deletions(-) create mode 100644 src/plugins/components/PluginsListPage/filters.ts create mode 100644 src/plugins/views/PluginList/filters.ts diff --git a/src/plugins/components/PluginsList/PluginsList.tsx b/src/plugins/components/PluginsList/PluginsList.tsx index 9fa315136..568e88910 100644 --- a/src/plugins/components/PluginsList/PluginsList.tsx +++ b/src/plugins/components/PluginsList/PluginsList.tsx @@ -1,4 +1,3 @@ -import Card from "@material-ui/core/Card"; import { makeStyles } from "@material-ui/core/styles"; import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; @@ -65,107 +64,105 @@ const PluginList: React.FC = props => { onPreviousPage } = props; const classes = useStyles(props); - const intl = useIntl(); + return ( - - - - + + onSort(PluginListUrlSortField.name)} + className={classes.colName} + > + {intl.formatMessage({ + defaultMessage: "Name", + description: "plugin name" + })} + + onSort(PluginListUrlSortField.active)} + className={classes.colActive} + > + {intl.formatMessage({ + defaultMessage: "Active", + description: "plugin status" + })} + + + {intl.formatMessage({ + defaultMessage: "Action", + description: "user action bar" + })} + + + + + onSort(PluginListUrlSortField.name)} - className={classes.colName} - > - {intl.formatMessage({ - defaultMessage: "Name", - description: "plugin name" - })} - - onSort(PluginListUrlSortField.active)} - className={classes.colActive} - > - {intl.formatMessage({ - defaultMessage: "Active", - description: "plugin status" - })} - - - {intl.formatMessage({ - defaultMessage: "Action", - description: "user action bar" - })} - - - - - - - - - {renderCollection( - plugins, - plugin => ( - - - {maybe(() => plugin.name, )} - - - {maybe( - () => ( - - ), - - )} - - -
- -
-
-
- ), - () => ( - - - {intl.formatMessage({ - defaultMessage: "No plugins found" - })} - - - ) - )} -
-
-
+ onPreviousPage={onPreviousPage} + /> + + + + {renderCollection( + plugins, + plugin => ( + + + {maybe(() => plugin.name, )} + + + {maybe( + () => ( + + ), + + )} + + +
+ +
+
+
+ ), + () => ( + + + {intl.formatMessage({ + defaultMessage: "No plugins found" + })} + + + ) + )} +
+ ); }; PluginList.displayName = "PluginList"; diff --git a/src/plugins/components/PluginsListPage/PluginsListPage.tsx b/src/plugins/components/PluginsListPage/PluginsListPage.tsx index efcb3372b..da4ead724 100644 --- a/src/plugins/components/PluginsListPage/PluginsListPage.tsx +++ b/src/plugins/components/PluginsListPage/PluginsListPage.tsx @@ -1,35 +1,85 @@ import React from "react"; +import Card from "@material-ui/core/Card"; import { useIntl } from "react-intl"; 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, SortPage } from "@saleor/types"; +import { + PageListProps, + SortPage, + FilterPageProps, + TabPageProps +} from "@saleor/types"; import { PluginListUrlSortField } from "@saleor/plugins/urls"; +import FilterBar from "@saleor/components/FilterBar"; import { Plugins_plugins_edges_node } from "../../types/Plugins"; import PluginsList from "../PluginsList/PluginsList"; +import { + createFilterStructure, + PluginFilterKeys, + PluginListFilterOpts +} from "./filters"; export interface PluginsListPageProps extends PageListProps, - SortPage { + FilterPageProps, + SortPage, + TabPageProps { plugins: Plugins_plugins_edges_node[]; onBack: () => void; } const PluginsListPage: React.FC = ({ + currencySymbol, + currentTab, + initialSearch, + filterOpts, + tabs, + onAdd, + onAll, onBack, + onSearchChange, + onFilterChange, + onTabChange, + onTabDelete, + onTabSave, ...listProps }) => { const intl = useIntl(); + const filterStructure = createFilterStructure(intl, filterOpts); + return ( {intl.formatMessage(sectionNames.configuration)} - + + + + ); }; diff --git a/src/plugins/components/PluginsListPage/filters.ts b/src/plugins/components/PluginsListPage/filters.ts new file mode 100644 index 000000000..d4d1a384b --- /dev/null +++ b/src/plugins/components/PluginsListPage/filters.ts @@ -0,0 +1,45 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { FilterOpts } from "@saleor/types"; +import { IFilter } from "@saleor/components/Filter"; +import { createBooleanField } from "@saleor/utils/filters/fields"; +import { commonMessages } from "@saleor/intl"; + +export enum PluginFilterKeys { + active = "active" +} + +export interface PluginListFilterOpts { + isActive: FilterOpts; +} + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "plugin" + }, + deactivated: { + defaultMessage: "Inactive", + description: "plugin" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: PluginListFilterOpts +): IFilter { + return [ + { + ...createBooleanField( + PluginFilterKeys.active, + intl.formatMessage(commonMessages.status), + opts.isActive.value, + { + negative: intl.formatMessage(messages.deactivated), + positive: intl.formatMessage(messages.active) + } + ), + active: opts.isActive.active + } + ]; +} diff --git a/src/plugins/components/PluginsListPage/index.ts b/src/plugins/components/PluginsListPage/index.ts index 37ed6f973..a378b1ac7 100644 --- a/src/plugins/components/PluginsListPage/index.ts +++ b/src/plugins/components/PluginsListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./PluginsListPage"; export * from "./PluginsListPage"; +export * from "./filters"; diff --git a/src/plugins/queries.ts b/src/plugins/queries.ts index dc518f6a5..c3003fa81 100644 --- a/src/plugins/queries.ts +++ b/src/plugins/queries.ts @@ -35,6 +35,7 @@ const pluginsList = gql` $after: String $last: Int $before: String + $filter: PluginFilterInput $sort: PluginSortingInput ) { plugins( @@ -42,6 +43,7 @@ const pluginsList = gql` after: $after first: $first last: $last + filter: $filter sortBy: $sort ) { edges { diff --git a/src/plugins/types/Plugins.ts b/src/plugins/types/Plugins.ts index a8d888402..59b578762 100644 --- a/src/plugins/types/Plugins.ts +++ b/src/plugins/types/Plugins.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { PluginSortingInput } from "./../../types/globalTypes"; +import { PluginFilterInput, PluginSortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: Plugins @@ -44,5 +44,6 @@ export interface PluginsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: PluginFilterInput | null; sort?: PluginSortingInput | null; } diff --git a/src/plugins/urls.ts b/src/plugins/urls.ts index 11a6b15f8..ab0faca5f 100644 --- a/src/plugins/urls.ts +++ b/src/plugins/urls.ts @@ -1,17 +1,34 @@ import { stringify as stringifyQs } from "qs"; import urlJoin from "url-join"; -import { Dialog, Pagination, SingleAction, Sort } from "../types"; +import { + Dialog, + Pagination, + SingleAction, + Sort, + Filters, + ActiveTab, + TabActionDialog +} from "../types"; export const pluginSection = "/plugins/"; export const pluginListPath = pluginSection; +export enum PluginListUrlFiltersEnum { + active = "active", + query = "query" +} +export type PluginListUrlFilters = Filters; +export type PluginListUrlDialog = TabActionDialog; export enum PluginListUrlSortField { name = "name", active = "active" } export type PluginListUrlSort = Sort; -export type PluginListUrlQueryParams = Pagination & +export type PluginListUrlQueryParams = ActiveTab & + Dialog & + PluginListUrlFilters & + Pagination & PluginListUrlSort & SingleAction; export const pluginListUrl = (params?: PluginListUrlQueryParams) => diff --git a/src/plugins/views/PluginList/PluginList.tsx b/src/plugins/views/PluginList/PluginList.tsx index 60482660a..e46efa943 100644 --- a/src/plugins/views/PluginList/PluginList.tsx +++ b/src/plugins/views/PluginList/PluginList.tsx @@ -10,10 +10,32 @@ import React from "react"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; +import useShop from "@saleor/hooks/useShop"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import SaveFilterTabDialog, { + SaveFilterTabDialogFormData +} from "@saleor/components/SaveFilterTabDialog"; +import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; import PluginsListPage from "../../components/PluginsListPage/PluginsListPage"; import { usePluginsListQuery } from "../../queries"; -import { PluginListUrlQueryParams, pluginListUrl, pluginUrl } from "../../urls"; +import { + PluginListUrlQueryParams, + pluginListUrl, + pluginUrl, + PluginListUrlDialog +} from "../../urls"; import { getSortQueryVariables } from "./sort"; +import { + getFilterQueryParam, + getFilterOpts, + getFilterTabs, + areFiltersApplied, + saveFilterTab, + getActiveFilters, + deleteFilterTab, + getFilterVariables +} from "./filters"; interface PluginsListProps { params: PluginListUrlQueryParams; @@ -22,6 +44,7 @@ interface PluginsListProps { export const PluginsList: React.FC = ({ params }) => { const navigate = useNavigator(); const paginate = usePaginator(); + const shop = useShop(); const { updateListSettings, settings } = useListSettings( ListViews.PLUGINS_LIST ); @@ -30,6 +53,7 @@ export const PluginsList: React.FC = ({ params }) => { const queryVariables = React.useMemo( () => ({ ...paginationState, + filter: getFilterVariables(params), sort: getSortQueryVariables(params) }), [params] @@ -39,6 +63,50 @@ export const PluginsList: React.FC = ({ params }) => { variables: queryVariables }); + const tabs = getFilterTabs(); + + const currentTab = + params.activeTab === undefined + ? areFiltersApplied(params) + ? tabs.length + 1 + : 0 + : parseInt(params.activeTab, 0); + + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + createUrl: pluginListUrl, + getFilterQueryParam, + navigate, + params + }); + + const [openModal, closeModal] = createDialogActionHandlers< + PluginListUrlDialog, + PluginListUrlQueryParams + >(navigate, pluginListUrl, params); + + const handleTabChange = (tab: number) => { + navigate( + pluginListUrl({ + activeTab: tab.toString(), + ...getFilterTabs()[tab - 1].data + }) + ); + }; + + const handleFilterTabDelete = () => { + deleteFilterTab(currentTab); + navigate(pluginListUrl()); + }; + + const handleFilterTabSave = (data: SaveFilterTabDialogFormData) => { + saveFilterTab(data.name, getActiveFilters(params)); + handleTabChange(tabs.length + 1); + }; + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.plugins.pageInfo), paginationState, @@ -46,23 +114,48 @@ export const PluginsList: React.FC = ({ params }) => { ); const handleSort = createSortHandler(navigate, pluginListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( <> data.plugins.edges.map(edge => edge.node))} pageInfo={pageInfo} sort={getSortParams(params)} + tabs={getFilterTabs().map(tab => tab.name)} onAdd={() => navigate(configurationMenuUrl)} + onAll={resetFilters} onBack={() => navigate(configurationMenuUrl)} + onFilterChange={changeFilters} + onSearchChange={handleSearchChange} onNextPage={loadNextPage} onPreviousPage={loadPreviousPage} onSort={handleSort} + onTabSave={() => openModal("save-search")} + onTabDelete={() => openModal("delete-search")} + onTabChange={handleTabChange} onUpdateListSettings={updateListSettings} onRowClick={id => () => navigate(pluginUrl(id))} /> + + tabs[currentTab - 1].name, "...")} + /> ); }; diff --git a/src/plugins/views/PluginList/filters.ts b/src/plugins/views/PluginList/filters.ts new file mode 100644 index 000000000..0a995fffa --- /dev/null +++ b/src/plugins/views/PluginList/filters.ts @@ -0,0 +1,71 @@ +import { PluginFilterInput } from "@saleor/types/globalTypes"; +import { + PluginListFilterOpts, + PluginFilterKeys +} from "@saleor/plugins/components/PluginsListPage"; +import { maybe, parseBoolean } from "@saleor/misc"; +import { IFilterElement } from "@saleor/components/Filter"; +import { + PluginListUrlFilters, + PluginListUrlFiltersEnum, + PluginListUrlQueryParams +} from "../../urls"; +import { + createFilterTabUtils, + createFilterUtils +} from "../../../utils/filters"; + +export const PLUGIN_FILTERS_KEY = "pluginFilters"; + +export function getFilterOpts( + params: PluginListUrlFilters +): PluginListFilterOpts { + return { + isActive: { + active: maybe(() => params.active !== undefined, false), + value: + params.active !== undefined ? parseBoolean(params.active, true) : true + } + }; +} + +export function getFilterVariables( + params: PluginListUrlFilters +): PluginFilterInput { + return { + active: + params.active !== undefined + ? parseBoolean(params.active, true) + : undefined, + search: params.query + }; +} + +export function getFilterQueryParam( + filter: IFilterElement +): PluginListUrlFilters { + const { active, name, value } = filter; + + switch (name) { + case PluginFilterKeys.active: + if (!active) { + return { + active: undefined + }; + } + return { + active: value[0] + }; + } +} + +export const { + deleteFilterTab, + getFilterTabs, + saveFilterTab +} = createFilterTabUtils(PLUGIN_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + PluginListUrlQueryParams, + PluginListUrlFilters +>(PluginListUrlFiltersEnum); diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 136badcd5..bc8d960d2 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -986,6 +986,11 @@ export interface PageTranslationInput { contentJson?: any | null; } +export interface PluginFilterInput { + active?: boolean | null; + search?: string | null; +} + export interface PluginSortingInput { direction: OrderDirection; field: PluginSortField; From a503f085388cefe2570bde94e1f788971c4b20df Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 17:34:30 +0100 Subject: [PATCH 59/85] Update stories --- .../__snapshots__/Stories.test.ts.snap | 252 ++++++++++++++++++ .../stories/plugins/PluginsListPage.tsx | 13 +- 2 files changed, 264 insertions(+), 1 deletion(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 4cfd36ed6..2871c1ac9 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -80959,6 +80959,90 @@ exports[`Storyshots Views / Plugins / Plugin list default 1`] = `
+
+
+
+ + +
+
+
+
+
+ +
+
+
+ + +
+
+
@@ -81244,6 +81328,90 @@ exports[`Storyshots Views / Plugins / Plugin list loading 1`] = `
+
+
+
+ + +
+
+
+
+
+ +
+
+
+ + +
+
+
@@ -81499,6 +81667,90 @@ exports[`Storyshots Views / Plugins / Plugin list no data 1`] = `
+
+
+
+ + +
+
+
+
+
+ +
+
+
+ + +
+
+
diff --git a/src/storybook/stories/plugins/PluginsListPage.tsx b/src/storybook/stories/plugins/PluginsListPage.tsx index cf874005f..c53139c68 100644 --- a/src/storybook/stories/plugins/PluginsListPage.tsx +++ b/src/storybook/stories/plugins/PluginsListPage.tsx @@ -2,7 +2,11 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { PluginListUrlSortField } from "@saleor/plugins/urls"; -import { pageListProps, sortPageProps } from "../../../fixtures"; +import { + pageListProps, + sortPageProps, + filterPageProps +} from "../../../fixtures"; import PluginsListPage, { PluginsListPageProps } from "../../../plugins/components/PluginsListPage"; @@ -12,6 +16,13 @@ import Decorator from "../../Decorator"; const props: PluginsListPageProps = { ...pageListProps.default, ...sortPageProps, + ...filterPageProps, + filterOpts: { + isActive: { + active: false, + value: true + } + }, onBack: () => undefined, plugins: pluginList, sort: { From ed5cd0006232ff93d2a0500afd063ba3d3fb8054 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 13 Jan 2020 15:26:10 +0100 Subject: [PATCH 60/85] Add test utils --- package.json | 1 + src/fixtures.ts | 5 +++++ testUtils/filters.ts | 21 +++++++++++++++++++++ testUtils/intl.ts | 6 ++++++ tsconfig.json | 3 ++- 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 testUtils/filters.ts create mode 100644 testUtils/intl.ts diff --git a/package.json b/package.json index 7666c5561..5fdffbe48 100644 --- a/package.json +++ b/package.json @@ -164,6 +164,7 @@ "@assets(.*)$": "/assets/$1", "@locale(.*)$": "/locale/$1", "@saleor(.*)$": "/src/$1", + "@test(.*)$": "/testUtils/$1", "^lodash-es(.*)$": "lodash/$1" } }, diff --git a/src/fixtures.ts b/src/fixtures.ts index 5b45c2f75..c466112cd 100644 --- a/src/fixtures.ts +++ b/src/fixtures.ts @@ -376,3 +376,8 @@ export const permissions: ShopInfo_shop_permissions[] = [ __typename: "PermissionDisplay" as "PermissionDisplay", ...perm })); + +export const date = { + from: "2019-12-09", + to: "2019-12-38" +}; diff --git a/testUtils/filters.ts b/testUtils/filters.ts new file mode 100644 index 000000000..0c3987080 --- /dev/null +++ b/testUtils/filters.ts @@ -0,0 +1,21 @@ +import clone from "lodash-es/clone"; + +import { IFilter } from "@saleor/components/Filter"; + +export function getExistingKeys(o: object): string[] { + return Object.keys(o).filter(key => o[key] !== undefined && o[key] !== null); +} + +export function setFilterOptsStatus( + opts: IFilter, + status: boolean +): IFilter { + const newOpts = clone(opts); + for (const optName in opts) { + if (Object.prototype.hasOwnProperty.call(newOpts, optName)) { + newOpts[optName].active = status; + } + } + + return newOpts; +} diff --git a/testUtils/intl.ts b/testUtils/intl.ts new file mode 100644 index 000000000..796d46e21 --- /dev/null +++ b/testUtils/intl.ts @@ -0,0 +1,6 @@ +import { OptionalIntlConfig } from "react-intl/dist/components/provider"; + +export const config: OptionalIntlConfig = { + defaultLocale: "en", + locale: "en" +}; diff --git a/tsconfig.json b/tsconfig.json index 56464c40a..a015886ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ "paths": { "@assets/*": ["assets/*"], "@locale/*": ["locale/*"], - "@saleor/*": ["src/*"] + "@saleor/*": ["src/*"], + "@test/*": ["testUtils/*"] }, "resolveJsonModule": true }, From 83e1f62da4d71d3fe8e5a2b93b60b0a82b1e0374 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 13 Jan 2020 15:26:31 +0100 Subject: [PATCH 61/85] Add filter tests --- react-intl.d.ts | 3 + .../__snapshots__/filters.test.ts.snap | 14 +++ .../views/AttributeList/filters.test.ts | 77 +++++++++++++++++ .../__snapshots__/filters.test.ts.snap | 9 ++ .../views/CollectionList/filters.test.ts | 65 ++++++++++++++ .../__snapshots__/filters.test.ts.snap | 14 +++ .../views/CustomerList/filters.test.ts | 78 +++++++++++++++++ src/customers/views/CustomerList/filters.ts | 15 ++-- .../__snapshots__/filters.test.ts.snap | 15 ++++ src/discounts/views/SaleList/filters.test.ts | 76 +++++++++++++++++ src/discounts/views/SaleList/filters.ts | 7 +- .../__snapshots__/filters.test.ts.snap | 20 +++++ .../views/VoucherList/filters.test.ts | 85 +++++++++++++++++++ src/discounts/views/VoucherList/filters.ts | 11 +-- src/orders/components/OrderListPage/index.ts | 1 + .../__snapshots__/filters.test.ts.snap | 11 +++ .../views/OrderDraftList/filters.test.ts | 67 +++++++++++++++ src/orders/views/OrderDraftList/filters.ts | 7 +- .../__snapshots__/filters.test.ts.snap | 12 ++- src/orders/views/OrderList/filters.test.ts | 84 +++++++++++++++--- src/orders/views/OrderList/filters.ts | 7 +- .../__snapshots__/filters.test.ts.snap | 9 ++ src/plugins/views/PluginList/filters.test.ts | 57 +++++++++++++ .../__snapshots__/filters.test.ts.snap | 10 +++ .../views/ProductTypeList/filters.test.ts | 66 ++++++++++++++ .../__snapshots__/filters.test.ts.snap | 15 ++-- .../views/ProductList/filters.test.ts | 81 ++++++++++++++++-- src/products/views/ProductList/filters.ts | 7 +- .../components/ServiceListPage/index.ts | 1 + .../__snapshots__/filters.test.ts.snap | 9 ++ .../views/ServiceList/filters.test.ts | 57 +++++++++++++ .../__snapshots__/filters.test.ts.snap | 9 ++ src/staff/views/StaffList/filters.test.ts | 58 +++++++++++++ src/utils/filters/filters.ts | 13 +++ .../__snapshots__/filters.test.ts.snap | 9 ++ .../views/WebhookList/filters.test.ts | 57 +++++++++++++ 36 files changed, 1075 insertions(+), 61 deletions(-) create mode 100644 src/attributes/views/AttributeList/__snapshots__/filters.test.ts.snap create mode 100644 src/attributes/views/AttributeList/filters.test.ts create mode 100644 src/collections/views/CollectionList/__snapshots__/filters.test.ts.snap create mode 100644 src/collections/views/CollectionList/filters.test.ts create mode 100644 src/customers/views/CustomerList/__snapshots__/filters.test.ts.snap create mode 100644 src/customers/views/CustomerList/filters.test.ts create mode 100644 src/discounts/views/SaleList/__snapshots__/filters.test.ts.snap create mode 100644 src/discounts/views/SaleList/filters.test.ts create mode 100644 src/discounts/views/VoucherList/__snapshots__/filters.test.ts.snap create mode 100644 src/discounts/views/VoucherList/filters.test.ts create mode 100644 src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap create mode 100644 src/orders/views/OrderDraftList/filters.test.ts create mode 100644 src/plugins/views/PluginList/__snapshots__/filters.test.ts.snap create mode 100644 src/plugins/views/PluginList/filters.test.ts create mode 100644 src/productTypes/views/ProductTypeList/__snapshots__/filters.test.ts.snap create mode 100644 src/productTypes/views/ProductTypeList/filters.test.ts create mode 100644 src/services/views/ServiceList/__snapshots__/filters.test.ts.snap create mode 100644 src/services/views/ServiceList/filters.test.ts create mode 100644 src/staff/views/StaffList/__snapshots__/filters.test.ts.snap create mode 100644 src/staff/views/StaffList/filters.test.ts create mode 100644 src/webhooks/views/WebhookList/__snapshots__/filters.test.ts.snap create mode 100644 src/webhooks/views/WebhookList/filters.test.ts diff --git a/react-intl.d.ts b/react-intl.d.ts index efafa921d..152e90425 100644 --- a/react-intl.d.ts +++ b/react-intl.d.ts @@ -1,4 +1,5 @@ declare module "react-intl" { + import { OptionalIntlConfig } from "react-intl/dist/components/provider"; import * as ReactIntl from "node_modules/react-intl"; export * from "node_modules/react-intl"; @@ -51,4 +52,6 @@ declare module "react-intl" { > extends React.Component> {} export function useIntl(): IntlShape; + + export function createIntl(config: OptionalIntlConfig): IntlShape; } diff --git a/src/attributes/views/AttributeList/__snapshots__/filters.test.ts.snap b/src/attributes/views/AttributeList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..425e23c5c --- /dev/null +++ b/src/attributes/views/AttributeList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "availableInGrid": "true", + "filterableInDashboard": "true", + "filterableInStorefront": "true", + "isVariantOnly": "true", + "valueRequired": "true", + "visibleInStorefront": "true", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"availableInGrid=true&filterableInDashboard=true&filterableInStorefront=true&isVariantOnly=true&valueRequired=true&visibleInStorefront=true"`; diff --git a/src/attributes/views/AttributeList/filters.test.ts b/src/attributes/views/AttributeList/filters.test.ts new file mode 100644 index 000000000..5a50d1f1c --- /dev/null +++ b/src/attributes/views/AttributeList/filters.test.ts @@ -0,0 +1,77 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { AttributeListUrlFilters } from "@saleor/attributes/urls"; +import { createFilterStructure } from "@saleor/attributes/components/AttributeListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { config } from "@test/intl"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: AttributeListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: AttributeListUrlFilters = { + availableInGrid: true.toString() + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(1); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + availableInGrid: { + active: false, + value: true + }, + filterableInDashboard: { + active: false, + value: true + }, + filterableInStorefront: { + active: false, + value: true + }, + isVariantOnly: { + active: false, + value: true + }, + valueRequired: { + active: false, + value: true + }, + visibleInStorefront: { + active: false, + value: true + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/collections/views/CollectionList/__snapshots__/filters.test.ts.snap b/src/collections/views/CollectionList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..37f201d26 --- /dev/null +++ b/src/collections/views/CollectionList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "status": "PUBLISHED", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=PUBLISHED"`; diff --git a/src/collections/views/CollectionList/filters.test.ts b/src/collections/views/CollectionList/filters.test.ts new file mode 100644 index 000000000..1dc360a69 --- /dev/null +++ b/src/collections/views/CollectionList/filters.test.ts @@ -0,0 +1,65 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { CollectionListUrlFilters } from "@saleor/collections/urls"; +import { createFilterStructure } from "@saleor/collections/components/CollectionListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { CollectionPublished } from "@saleor/types/globalTypes"; +import { config } from "@test/intl"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: CollectionListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: CollectionListUrlFilters = { + status: CollectionPublished.PUBLISHED + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(1); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + status: { + active: false, + value: CollectionPublished.PUBLISHED + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filters = createFilterStructure(intl, { + status: { + active: true, + value: CollectionPublished.PUBLISHED + } + }); + + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/customers/views/CustomerList/__snapshots__/filters.test.ts.snap b/src/customers/views/CustomerList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..d9973216a --- /dev/null +++ b/src/customers/views/CustomerList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "joinedFrom": "2019-12-09", + "joinedTo": "2019-12-38", + "moneySpentFrom": "2", + "moneySpentTo": "39.50", + "numberOfOrdersFrom": "1", + "numberOfOrdersTo": "5", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"joinedFrom=2019-12-09&joinedTo=2019-12-38&moneySpentFrom=2&moneySpentTo=39.50&numberOfOrdersFrom=1&numberOfOrdersTo=5"`; diff --git a/src/customers/views/CustomerList/filters.test.ts b/src/customers/views/CustomerList/filters.test.ts new file mode 100644 index 000000000..7f73e826a --- /dev/null +++ b/src/customers/views/CustomerList/filters.test.ts @@ -0,0 +1,78 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { CustomerListUrlFilters } from "@saleor/customers/urls"; +import { createFilterStructure } from "@saleor/customers/components/CustomerListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { date } from "@saleor/fixtures"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: CustomerListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: CustomerListUrlFilters = { + joinedFrom: date.from, + moneySpentFrom: "2", + moneySpentTo: "39.50", + numberOfOrdersTo: "5" + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(3); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + joined: { + active: false, + value: { + max: date.to, + min: date.from + } + }, + moneySpent: { + active: false, + value: { + max: "39.50", + min: "2" + } + }, + numberOfOrders: { + active: false, + value: { + max: "5", + min: "1" + } + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/customers/views/CustomerList/filters.ts b/src/customers/views/CustomerList/filters.ts index 92c398ac7..10a1abcb9 100644 --- a/src/customers/views/CustomerList/filters.ts +++ b/src/customers/views/CustomerList/filters.ts @@ -7,7 +7,8 @@ import { } from "@saleor/customers/components/CustomerListPage"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getGteLteVariables } from "../../../utils/filters"; import { CustomerListUrlFilters, @@ -67,18 +68,18 @@ export function getFilterVariables( params: CustomerListUrlFilters ): CustomerFilterInput { return { - dateJoined: { + dateJoined: getGteLteVariables({ gte: params.joinedFrom, lte: params.joinedTo - }, - moneySpent: { + }), + moneySpent: getGteLteVariables({ gte: parseInt(params.moneySpentFrom, 0), lte: parseInt(params.moneySpentTo, 0) - }, - numberOfOrders: { + }), + numberOfOrders: getGteLteVariables({ gte: parseInt(params.numberOfOrdersFrom, 0), lte: parseInt(params.numberOfOrdersTo, 0) - }, + }), search: params.query }; } diff --git a/src/discounts/views/SaleList/__snapshots__/filters.test.ts.snap b/src/discounts/views/SaleList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..13faf2f2d --- /dev/null +++ b/src/discounts/views/SaleList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "startedFrom": "2019-12-09", + "startedTo": "2019-12-38", + "status": Array [ + "ACTIVE", + "EXPIRED", + ], + "type": "FIXED", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"startedFrom=2019-12-09&startedTo=2019-12-38&status%5B0%5D=ACTIVE&status%5B1%5D=EXPIRED&type=FIXED"`; diff --git a/src/discounts/views/SaleList/filters.test.ts b/src/discounts/views/SaleList/filters.test.ts new file mode 100644 index 000000000..f6d7f7743 --- /dev/null +++ b/src/discounts/views/SaleList/filters.test.ts @@ -0,0 +1,76 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { SaleListUrlFilters } from "@saleor/discounts/urls"; +import { createFilterStructure } from "@saleor/discounts/components/SaleListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { date } from "@saleor/fixtures"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { + DiscountValueTypeEnum, + DiscountStatusEnum +} from "@saleor/types/globalTypes"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: SaleListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: SaleListUrlFilters = { + startedFrom: date.from, + startedTo: date.to, + status: [DiscountStatusEnum.ACTIVE, DiscountStatusEnum.EXPIRED], + type: DiscountValueTypeEnum.FIXED + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(3); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + saleType: { + active: false, + value: DiscountValueTypeEnum.FIXED + }, + started: { + active: false, + value: { + max: date.to, + min: date.from + } + }, + status: { + active: false, + value: [DiscountStatusEnum.ACTIVE, DiscountStatusEnum.EXPIRED] + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/discounts/views/SaleList/filters.ts b/src/discounts/views/SaleList/filters.ts index 3d70f2412..8af9549dc 100644 --- a/src/discounts/views/SaleList/filters.ts +++ b/src/discounts/views/SaleList/filters.ts @@ -12,7 +12,8 @@ import { import { createFilterTabUtils, createFilterUtils, - dedupeFilter + dedupeFilter, + getGteLteVariables } from "../../../utils/filters"; import { SaleListUrlFilters, @@ -63,10 +64,10 @@ export function getFilterVariables( saleType: params.type && findValueInEnum(params.type, DiscountValueTypeEnum), search: params.query, - started: { + started: getGteLteVariables({ gte: joinDateTime(params.startedFrom), lte: joinDateTime(params.startedTo) - }, + }), status: params.status && params.status.map(status => findValueInEnum(status, DiscountStatusEnum)) diff --git a/src/discounts/views/VoucherList/__snapshots__/filters.test.ts.snap b/src/discounts/views/VoucherList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..1926087dc --- /dev/null +++ b/src/discounts/views/VoucherList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "startedFrom": "2019-12-09", + "startedTo": "2019-12-38", + "status": Array [ + "ACTIVE", + "EXPIRED", + ], + "timesUsedFrom": "1", + "timesUsedTo": "6", + "type": Array [ + "FIXED", + "SHIPPING", + ], +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"startedFrom=2019-12-09&startedTo=2019-12-38×UsedFrom=1×UsedTo=6&status%5B0%5D=ACTIVE&status%5B1%5D=EXPIRED&type%5B0%5D=FIXED&type%5B1%5D=SHIPPING"`; diff --git a/src/discounts/views/VoucherList/filters.test.ts b/src/discounts/views/VoucherList/filters.test.ts new file mode 100644 index 000000000..0000a4e6b --- /dev/null +++ b/src/discounts/views/VoucherList/filters.test.ts @@ -0,0 +1,85 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { VoucherListUrlFilters } from "@saleor/discounts/urls"; +import { createFilterStructure } from "@saleor/discounts/components/VoucherListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { date } from "@saleor/fixtures"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { + DiscountStatusEnum, + VoucherDiscountType +} from "@saleor/types/globalTypes"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: VoucherListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: VoucherListUrlFilters = { + startedFrom: date.from, + startedTo: date.to, + status: [DiscountStatusEnum.ACTIVE, DiscountStatusEnum.EXPIRED], + timesUsedFrom: date.from, + timesUsedTo: date.to, + type: [VoucherDiscountType.FIXED, VoucherDiscountType.SHIPPING] + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(4); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + saleType: { + active: false, + value: [VoucherDiscountType.FIXED, VoucherDiscountType.SHIPPING] + }, + started: { + active: false, + value: { + max: date.to, + min: date.from + } + }, + status: { + active: false, + value: [DiscountStatusEnum.ACTIVE, DiscountStatusEnum.EXPIRED] + }, + timesUsed: { + active: false, + value: { + max: "6", + min: "1" + } + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/discounts/views/VoucherList/filters.ts b/src/discounts/views/VoucherList/filters.ts index 6f17e8ba7..d35c56111 100644 --- a/src/discounts/views/VoucherList/filters.ts +++ b/src/discounts/views/VoucherList/filters.ts @@ -12,7 +12,8 @@ import { import { createFilterTabUtils, createFilterUtils, - dedupeFilter + dedupeFilter, + getGteLteVariables } from "../../../utils/filters"; import { VoucherListUrlFilters, @@ -85,17 +86,17 @@ export function getFilterVariables( params.type && params.type.map(type => findValueInEnum(type, VoucherDiscountType)), search: params.query, - started: { + started: getGteLteVariables({ gte: joinDateTime(params.startedFrom), lte: joinDateTime(params.startedTo) - }, + }), status: params.status && params.status.map(status => findValueInEnum(status, DiscountStatusEnum)), - timesUsed: { + timesUsed: getGteLteVariables({ gte: parseInt(params.timesUsedFrom, 0), lte: parseInt(params.timesUsedTo, 0) - } + }) }; } diff --git a/src/orders/components/OrderListPage/index.ts b/src/orders/components/OrderListPage/index.ts index 69c5cc429..14183bccf 100644 --- a/src/orders/components/OrderListPage/index.ts +++ b/src/orders/components/OrderListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./OrderListPage"; export * from "./OrderListPage"; +export * from "./filters"; diff --git a/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap b/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..b48bb1405 --- /dev/null +++ b/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "createdFrom": "2019-12-09", + "createdTo": "2019-12-38", + "customer": "admin@example.com", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"createdFrom=2019-12-09&createdTo=2019-12-38&customer=admin%40example.com"`; diff --git a/src/orders/views/OrderDraftList/filters.test.ts b/src/orders/views/OrderDraftList/filters.test.ts new file mode 100644 index 000000000..bc0bd6e4c --- /dev/null +++ b/src/orders/views/OrderDraftList/filters.test.ts @@ -0,0 +1,67 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { OrderDraftListUrlFilters } from "@saleor/orders/urls"; +import { createFilterStructure } from "@saleor/orders/components/OrderDraftListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { date } from "@saleor/fixtures"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: OrderDraftListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: OrderDraftListUrlFilters = { + createdFrom: date.from, + createdTo: date.to, + customer: "admin@example.com" + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(2); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + created: { + active: false, + value: { + max: date.to, + min: date.from + } + }, + customer: { + active: false, + value: "admin@example.com" + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/orders/views/OrderDraftList/filters.ts b/src/orders/views/OrderDraftList/filters.ts index 151583a0e..ce1267c0c 100644 --- a/src/orders/views/OrderDraftList/filters.ts +++ b/src/orders/views/OrderDraftList/filters.ts @@ -12,7 +12,8 @@ import { } from "../../urls"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getGteLteVariables } from "../../../utils/filters"; export const ORDER_DRAFT_FILTERS_KEY = "orderDraftFilters"; @@ -45,10 +46,10 @@ export function getFilterVariables( params: OrderDraftListUrlFilters ): OrderDraftFilterInput { return { - created: { + created: getGteLteVariables({ gte: params.createdFrom, lte: params.createdTo - }, + }), customer: params.customer, search: params.query }; diff --git a/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap b/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap index 732d10dce..2983d5df4 100644 --- a/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap +++ b/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap @@ -1,16 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Get filter variables 1`] = ` +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` Object { - "created": Object { - "gte": "2019-09-01", - "lte": "2019-09-10", - }, - "customer": "email@example.com", - "search": "24", + "createdFrom": "2019-12-09", + "createdTo": "2019-12-38", "status": Array [ "FULFILLED", "PARTIALLY_FULFILLED", ], } `; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"createdFrom=2019-12-09&createdTo=2019-12-38&status%5B0%5D=FULFILLED&status%5B1%5D=PARTIALLY_FULFILLED"`; diff --git a/src/orders/views/OrderList/filters.test.ts b/src/orders/views/OrderList/filters.test.ts index 793bbb94c..df4122e2a 100644 --- a/src/orders/views/OrderList/filters.test.ts +++ b/src/orders/views/OrderList/filters.test.ts @@ -1,17 +1,75 @@ -import { OrderStatus } from "@saleor/types/globalTypes"; -import { getFilterVariables } from "./filters"; +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; -test("Get filter variables", () => { - const filter = getFilterVariables({ - createdFrom: "2019-09-01", - createdTo: "2019-09-10", - email: "email@example.com", - query: "24", - status: [ - OrderStatus.FULFILLED.toString(), - OrderStatus.PARTIALLY_FULFILLED.toString() - ] +import { OrderListUrlFilters } from "@saleor/orders/urls"; +import { createFilterStructure } from "@saleor/orders/components/OrderListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { date } from "@saleor/fixtures"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { OrderStatusFilter } from "@saleor/types/globalTypes"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: OrderListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); }); - expect(filter).toMatchSnapshot(); + it("should not be empty object if params given", () => { + const params: OrderListUrlFilters = { + createdFrom: date.from, + createdTo: date.to, + email: "email@example.com", + status: [ + OrderStatusFilter.FULFILLED, + OrderStatusFilter.PARTIALLY_FULFILLED + ] + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(3); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + created: { + active: false, + value: { + max: date.to, + min: date.from + } + }, + status: { + active: false, + value: [ + OrderStatusFilter.FULFILLED, + OrderStatusFilter.PARTIALLY_FULFILLED + ] + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); }); diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index 28b549c7f..d4dc874f1 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -12,7 +12,8 @@ import { import { createFilterTabUtils, createFilterUtils, - dedupeFilter + dedupeFilter, + getGteLteVariables } from "../../../utils/filters"; import { OrderListUrlFilters, @@ -59,10 +60,10 @@ export function getFilterVariables( params: OrderListUrlFilters ): OrderFilterInput { return { - created: { + created: getGteLteVariables({ gte: params.createdFrom, lte: params.createdTo - }, + }), customer: params.email, search: params.query, status: maybe(() => diff --git a/src/plugins/views/PluginList/__snapshots__/filters.test.ts.snap b/src/plugins/views/PluginList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..79b6ca913 --- /dev/null +++ b/src/plugins/views/PluginList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "active": "true", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"active=true"`; diff --git a/src/plugins/views/PluginList/filters.test.ts b/src/plugins/views/PluginList/filters.test.ts new file mode 100644 index 000000000..fe14aa8b5 --- /dev/null +++ b/src/plugins/views/PluginList/filters.test.ts @@ -0,0 +1,57 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { PluginListUrlFilters } from "@saleor/plugins/urls"; +import { createFilterStructure } from "@saleor/plugins/components/PluginsListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: PluginListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: PluginListUrlFilters = { + active: true.toString() + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(1); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + isActive: { + active: false, + value: true + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/productTypes/views/ProductTypeList/__snapshots__/filters.test.ts.snap b/src/productTypes/views/ProductTypeList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..305a60be9 --- /dev/null +++ b/src/productTypes/views/ProductTypeList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "configurable": "CONFIGURABLE", + "type": "DIGITAL", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"configurable=CONFIGURABLE&type=DIGITAL"`; diff --git a/src/productTypes/views/ProductTypeList/filters.test.ts b/src/productTypes/views/ProductTypeList/filters.test.ts new file mode 100644 index 000000000..9911460a0 --- /dev/null +++ b/src/productTypes/views/ProductTypeList/filters.test.ts @@ -0,0 +1,66 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { ProductTypeListUrlFilters } from "@saleor/productTypes/urls"; +import { createFilterStructure } from "@saleor/productTypes/components/ProductTypeListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { + ProductTypeEnum, + ProductTypeConfigurable +} from "@saleor/types/globalTypes"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: ProductTypeListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: ProductTypeListUrlFilters = { + configurable: ProductTypeConfigurable.CONFIGURABLE, + type: ProductTypeEnum.DIGITAL + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(2); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + configurable: { + active: false, + value: ProductTypeConfigurable.CONFIGURABLE + }, + type: { + active: false, + value: ProductTypeEnum.DIGITAL + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap index 5f3c7e2c2..cb6472a86 100644 --- a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap +++ b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap @@ -1,13 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Get filter variables 1`] = ` +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` Object { - "isPublished": false, - "price": Object { - "gte": 10, - "lte": 20, - }, - "search": undefined, - "stockAvailability": "IN_STOCK", + "priceFrom": "10", + "priceTo": "20", + "status": "published", + "stockStatus": "IN_STOCK", } `; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20"`; diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts index 83b9a8cbb..07a094dcd 100644 --- a/src/products/views/ProductList/filters.test.ts +++ b/src/products/views/ProductList/filters.test.ts @@ -1,13 +1,76 @@ -import { StockAvailability } from "@saleor/types/globalTypes"; -import { getFilterVariables } from "./filters"; +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; -test("Get filter variables", () => { - const filter = getFilterVariables({ - priceFrom: "10", - priceTo: "20", - status: "true", - stockStatus: StockAvailability.IN_STOCK +import { ProductListUrlFilters } from "@saleor/products/urls"; +import { + createFilterStructure, + ProductStatus +} from "@saleor/products/components/ProductListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { date } from "@saleor/fixtures"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { StockAvailability } from "@saleor/types/globalTypes"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: ProductListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); }); - expect(filter).toMatchSnapshot(); + it("should not be empty object if params given", () => { + const params: ProductListUrlFilters = { + priceFrom: "10", + priceTo: "20", + status: true.toString(), + stockStatus: StockAvailability.IN_STOCK + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(3); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + price: { + active: false, + value: { + max: "20", + min: "10" + } + }, + status: { + active: false, + value: ProductStatus.PUBLISHED + }, + stockStatus: { + active: false, + value: StockAvailability.IN_STOCK + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); }); diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 476208e0d..1b94e5b62 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -11,7 +11,8 @@ import { } from "../../../types/globalTypes"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getGteLteVariables } from "../../../utils/filters"; import { ProductListUrlFilters, @@ -55,10 +56,10 @@ export function getFilterVariables( params.status !== undefined ? params.status === ProductStatus.PUBLISHED : null, - price: { + price: getGteLteVariables({ gte: parseFloat(params.priceFrom), lte: parseFloat(params.priceTo) - }, + }), search: params.query, stockAvailability: params.stockStatus !== undefined diff --git a/src/services/components/ServiceListPage/index.ts b/src/services/components/ServiceListPage/index.ts index 0cbe92960..44625afe2 100644 --- a/src/services/components/ServiceListPage/index.ts +++ b/src/services/components/ServiceListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./ServiceListPage"; export * from "./ServiceListPage"; +export * from "./filters"; diff --git a/src/services/views/ServiceList/__snapshots__/filters.test.ts.snap b/src/services/views/ServiceList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..c63a81372 --- /dev/null +++ b/src/services/views/ServiceList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "active": "false", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"active=false"`; diff --git a/src/services/views/ServiceList/filters.test.ts b/src/services/views/ServiceList/filters.test.ts new file mode 100644 index 000000000..e52fe2de1 --- /dev/null +++ b/src/services/views/ServiceList/filters.test.ts @@ -0,0 +1,57 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { ServiceListUrlFilters } from "@saleor/services/urls"; +import { createFilterStructure } from "@saleor/services/components/ServiceListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: ServiceListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: ServiceListUrlFilters = { + active: false.toString() + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(1); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + isActive: { + active: false, + value: false + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/staff/views/StaffList/__snapshots__/filters.test.ts.snap b/src/staff/views/StaffList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..124fbeb79 --- /dev/null +++ b/src/staff/views/StaffList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "status": "ACTIVE", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=ACTIVE"`; diff --git a/src/staff/views/StaffList/filters.test.ts b/src/staff/views/StaffList/filters.test.ts new file mode 100644 index 000000000..32a812b2d --- /dev/null +++ b/src/staff/views/StaffList/filters.test.ts @@ -0,0 +1,58 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { StaffListUrlFilters } from "@saleor/staff/urls"; +import { createFilterStructure } from "@saleor/staff/components/StaffListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { StaffMemberStatus } from "@saleor/types/globalTypes"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: StaffListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: StaffListUrlFilters = { + status: StaffMemberStatus.ACTIVE + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(1); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + status: { + active: false, + value: StaffMemberStatus.ACTIVE + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/utils/filters/filters.ts b/src/utils/filters/filters.ts index 63456e042..00d38887f 100644 --- a/src/utils/filters/filters.ts +++ b/src/utils/filters/filters.ts @@ -43,4 +43,17 @@ export function getFilterQueryParams< ); } +type GteLte = Partial>; +export function getGteLteVariables(variables: GteLte): GteLte | null { + if ( + !![variables.gte, variables.lte].some( + v => v !== undefined && v !== null && !(typeof v === "number" && isNaN(v)) + ) + ) { + return variables; + } + + return null; +} + export default createFilterUtils; diff --git a/src/webhooks/views/WebhookList/__snapshots__/filters.test.ts.snap b/src/webhooks/views/WebhookList/__snapshots__/filters.test.ts.snap new file mode 100644 index 000000000..c63a81372 --- /dev/null +++ b/src/webhooks/views/WebhookList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "active": "false", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"active=false"`; diff --git a/src/webhooks/views/WebhookList/filters.test.ts b/src/webhooks/views/WebhookList/filters.test.ts new file mode 100644 index 000000000..2aa937136 --- /dev/null +++ b/src/webhooks/views/WebhookList/filters.test.ts @@ -0,0 +1,57 @@ +import { createIntl } from "react-intl"; +import { stringify as stringifyQs } from "qs"; + +import { WebhookListUrlFilters } from "@saleor/webhooks/urls"; +import { createFilterStructure } from "@saleor/webhooks/components/WebhooksListPage"; +import { getFilterQueryParams } from "@saleor/utils/filters"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { getFilterVariables, getFilterQueryParam } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: WebhookListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + + it("should not be empty object if params given", () => { + const params: WebhookListUrlFilters = { + active: false.toString() + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(1); + }); +}); + +describe("Filtering URL params", () => { + const intl = createIntl(config); + + const filters = createFilterStructure(intl, { + isActive: { + active: false, + value: false + } + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams( + filters, + getFilterQueryParam + ); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); From 8361ef09173429053ebee5b042f1005e788f9b4d Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 13 Jan 2020 15:29:29 +0100 Subject: [PATCH 62/85] Remove unused import --- src/products/views/ProductList/filters.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts index 07a094dcd..43b4f23ab 100644 --- a/src/products/views/ProductList/filters.test.ts +++ b/src/products/views/ProductList/filters.test.ts @@ -7,7 +7,6 @@ import { ProductStatus } from "@saleor/products/components/ProductListPage"; import { getFilterQueryParams } from "@saleor/utils/filters"; -import { date } from "@saleor/fixtures"; import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; import { config } from "@test/intl"; import { StockAvailability } from "@saleor/types/globalTypes"; From bb5a615040f58367e55d6dd04b7fd87557df637c Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 13 Jan 2020 16:31:43 +0100 Subject: [PATCH 63/85] Fix module aliasing --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5fdffbe48..e6e32cdc6 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,7 @@ "@assets(.*)$": "/assets/$1", "@locale(.*)$": "/locale/$1", "@saleor(.*)$": "/src/$1", - "@test(.*)$": "/testUtils/$1", + "@test/(.*)$": "/testUtils/$1", "^lodash-es(.*)$": "lodash/$1" } }, From 943232acb3f3229c2a9ac3b693717832ab5c2bb0 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 13 Jan 2020 16:22:47 +0100 Subject: [PATCH 64/85] Add query param getters --- .../components/AttributeListPage/filters.ts | 2 +- src/attributes/views/AttributeList/filters.ts | 83 ++++++------------- .../views/CollectionList/filters.ts | 19 ++--- src/customers/views/CustomerList/filters.ts | 71 ++++------------ src/discounts/views/SaleList/filters.ts | 59 +++++-------- src/discounts/views/VoucherList/filters.ts | 80 ++++++------------ src/orders/views/OrderDraftList/filters.ts | 40 +++------ src/orders/views/OrderList/filters.ts | 41 ++++----- src/plugins/views/PluginList/filters.ts | 14 +--- .../views/ProductTypeList/filters.ts | 31 +++---- src/products/views/ProductList/filters.ts | 54 +++++------- src/services/views/ServiceList/filters.ts | 14 +--- src/staff/views/StaffList/filters.ts | 19 ++--- src/utils/filters/filters.ts | 80 ++++++++++++++++++ src/webhooks/views/WebhookList/filters.ts | 17 ++-- 15 files changed, 262 insertions(+), 362 deletions(-) diff --git a/src/attributes/components/AttributeListPage/filters.ts b/src/attributes/components/AttributeListPage/filters.ts index 30acfd23a..d3309a492 100644 --- a/src/attributes/components/AttributeListPage/filters.ts +++ b/src/attributes/components/AttributeListPage/filters.ts @@ -6,7 +6,7 @@ import { IFilter } from "@saleor/components/Filter"; import { createBooleanField } from "@saleor/utils/filters/fields"; export enum AttributeFilterKeys { - availableInGrid = "availableInGrud", + availableInGrid = "availableInGrid", filterableInDashboard = "filterableInDashboard", filterableInStorefront = "filterableInStorefront", isVariantOnly = "isVariantOnly", diff --git a/src/attributes/views/AttributeList/filters.ts b/src/attributes/views/AttributeList/filters.ts index 96c1ac929..5c39a51ca 100644 --- a/src/attributes/views/AttributeList/filters.ts +++ b/src/attributes/views/AttributeList/filters.ts @@ -7,7 +7,8 @@ import { } from "@saleor/attributes/components/AttributeListPage"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getSingleValueQueryParam } from "../../../utils/filters"; import { AttributeListUrlFilters, @@ -83,74 +84,44 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): AttributeListUrlFilters { - const { active, name, value } = filter; + const { name } = filter; switch (name) { case AttributeFilterKeys.availableInGrid: - if (!active) { - return { - availableInGrid: undefined - }; - } - - return { - availableInGrid: value[0] - }; + return getSingleValueQueryParam( + filter, + AttributeListUrlFiltersEnum.availableInGrid + ); case AttributeFilterKeys.filterableInDashboard: - if (!active) { - return { - filterableInDashboard: undefined - }; - } - - return { - filterableInDashboard: value[0] - }; + return getSingleValueQueryParam( + filter, + AttributeListUrlFiltersEnum.filterableInDashboard + ); case AttributeFilterKeys.filterableInStorefront: - if (!active) { - return { - filterableInStorefront: undefined - }; - } - - return { - filterableInStorefront: value[0] - }; + return getSingleValueQueryParam( + filter, + AttributeListUrlFiltersEnum.filterableInStorefront + ); case AttributeFilterKeys.isVariantOnly: - if (!active) { - return { - isVariantOnly: undefined - }; - } - - return { - isVariantOnly: value[0] - }; + return getSingleValueQueryParam( + filter, + AttributeListUrlFiltersEnum.isVariantOnly + ); case AttributeFilterKeys.valueRequired: - if (!active) { - return { - valueRequired: undefined - }; - } - - return { - valueRequired: value[0] - }; + return getSingleValueQueryParam( + filter, + AttributeListUrlFiltersEnum.valueRequired + ); case AttributeFilterKeys.visibleInStorefront: - if (!active) { - return { - visibleInStorefront: undefined - }; - } - - return { - visibleInStorefront: value[0] - }; + return getSingleValueQueryParam( + filter, + AttributeListUrlFiltersEnum.visibleInStorefront + ); } } diff --git a/src/collections/views/CollectionList/filters.ts b/src/collections/views/CollectionList/filters.ts index bbc066e26..9b42354a5 100644 --- a/src/collections/views/CollectionList/filters.ts +++ b/src/collections/views/CollectionList/filters.ts @@ -15,7 +15,8 @@ import { } from "../../urls"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getSingleEnumValueQueryParam } from "../../../utils/filters"; export const COLLECTION_FILTERS_KEY = "collectionFilters"; @@ -45,19 +46,15 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): CollectionListUrlFilters { - const { active, name, value } = filter; + const { name } = filter; switch (name) { case CollectionFilterKeys.status: - if (!active) { - return { - status: undefined - }; - } - - return { - status: value[0] - }; + return getSingleEnumValueQueryParam( + filter, + CollectionListUrlFiltersEnum.status, + CollectionPublished + ); } } diff --git a/src/customers/views/CustomerList/filters.ts b/src/customers/views/CustomerList/filters.ts index 10a1abcb9..fb72e2d0b 100644 --- a/src/customers/views/CustomerList/filters.ts +++ b/src/customers/views/CustomerList/filters.ts @@ -8,7 +8,8 @@ import { import { createFilterTabUtils, createFilterUtils, - getGteLteVariables + getGteLteVariables, + getMinMaxQueryParam } from "../../../utils/filters"; import { CustomerListUrlFilters, @@ -87,65 +88,29 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): CustomerListUrlFilters { - const { active, multiple, name, value } = filter; + const { name } = filter; switch (name) { case CustomerFilterKeys.joined: - if (!active) { - return { - joinedFrom: undefined, - joinedTo: undefined - }; - } - if (multiple) { - return { - joinedFrom: value[0], - joinedTo: value[1] - }; - } - - return { - joinedFrom: value[0], - joinedTo: value[0] - }; + return getMinMaxQueryParam( + filter, + CustomerListUrlFiltersEnum.joinedFrom, + CustomerListUrlFiltersEnum.joinedTo + ); case CustomerFilterKeys.moneySpent: - if (!active) { - return { - moneySpentFrom: undefined, - moneySpentTo: undefined - }; - } - if (multiple) { - return { - moneySpentFrom: value[0], - moneySpentTo: value[1] - }; - } - - return { - moneySpentFrom: value[0], - moneySpentTo: value[0] - }; + return getMinMaxQueryParam( + filter, + CustomerListUrlFiltersEnum.moneySpentFrom, + CustomerListUrlFiltersEnum.moneySpentTo + ); case CustomerFilterKeys.numberOfOrders: - if (!active) { - return { - numberOfOrdersFrom: undefined, - numberOfOrdersTo: undefined - }; - } - if (multiple) { - return { - numberOfOrdersFrom: value[0], - numberOfOrdersTo: value[1] - }; - } - - return { - numberOfOrdersFrom: value[0], - numberOfOrdersTo: value[0] - }; + return getMinMaxQueryParam( + filter, + CustomerListUrlFiltersEnum.numberOfOrdersFrom, + CustomerListUrlFiltersEnum.numberOfOrdersTo + ); } } diff --git a/src/discounts/views/SaleList/filters.ts b/src/discounts/views/SaleList/filters.ts index 8af9549dc..d2683e58f 100644 --- a/src/discounts/views/SaleList/filters.ts +++ b/src/discounts/views/SaleList/filters.ts @@ -13,12 +13,16 @@ import { createFilterTabUtils, createFilterUtils, dedupeFilter, - getGteLteVariables + getGteLteVariables, + getSingleEnumValueQueryParam, + getMinMaxQueryParam, + getMultipleEnumValueQueryParam } from "../../../utils/filters"; import { SaleListUrlFilters, SaleListUrlFiltersEnum, - SaleListUrlQueryParams + SaleListUrlQueryParams, + SaleListUrlFiltersWithMultipleValues } from "../../urls"; export const SALE_FILTERS_KEY = "saleFilters"; @@ -77,48 +81,29 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): SaleListUrlFilters { - const { active, multiple, name, value } = filter; + const { name } = filter; switch (name) { case SaleFilterKeys.saleType: - if (!active) { - return { - type: undefined - }; - } - - return { - type: findValueInEnum(value[0], DiscountValueTypeEnum) - }; + return getSingleEnumValueQueryParam( + filter, + SaleListUrlFiltersEnum.type, + DiscountValueTypeEnum + ); case SaleFilterKeys.started: - if (!active) { - return { - startedFrom: undefined, - startedTo: undefined - }; - } - if (multiple) { - return { - startedFrom: value[0], - startedTo: value[1] - }; - } - - return { - startedFrom: value[0], - startedTo: value[0] - }; + return getMinMaxQueryParam( + filter, + SaleListUrlFiltersEnum.startedFrom, + SaleListUrlFiltersEnum.startedTo + ); case SaleFilterKeys.status: - if (!active) { - return { - status: undefined - }; - } - return { - status: value.map(val => findValueInEnum(val, DiscountStatusEnum)) - }; + return getMultipleEnumValueQueryParam( + filter, + SaleListUrlFiltersWithMultipleValues.status, + DiscountStatusEnum + ); } } diff --git a/src/discounts/views/VoucherList/filters.ts b/src/discounts/views/VoucherList/filters.ts index d35c56111..691ec9a1e 100644 --- a/src/discounts/views/VoucherList/filters.ts +++ b/src/discounts/views/VoucherList/filters.ts @@ -13,12 +13,15 @@ import { createFilterTabUtils, createFilterUtils, dedupeFilter, - getGteLteVariables + getGteLteVariables, + getMultipleEnumValueQueryParam, + getMinMaxQueryParam } from "../../../utils/filters"; import { VoucherListUrlFilters, VoucherListUrlFiltersEnum, - VoucherListUrlQueryParams + VoucherListUrlQueryParams, + VoucherListUrlFiltersWithMultipleValues } from "../../urls"; export const VOUCHER_FILTERS_KEY = "voucherFilters"; @@ -103,67 +106,36 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): VoucherListUrlFilters { - const { active, multiple, name, value } = filter; + const { name } = filter; switch (name) { case VoucherFilterKeys.saleType: - if (!active) { - return { - type: undefined - }; - } - - return { - type: value.map(type => findValueInEnum(type, VoucherDiscountType)) - }; + return getMultipleEnumValueQueryParam( + filter, + VoucherListUrlFiltersWithMultipleValues.type, + VoucherDiscountType + ); case VoucherFilterKeys.started: - if (!active) { - return { - startedFrom: undefined, - startedTo: undefined - }; - } - if (multiple) { - return { - startedFrom: value[0], - startedTo: value[1] - }; - } - - return { - startedFrom: value[0], - startedTo: value[0] - }; + return getMinMaxQueryParam( + filter, + VoucherListUrlFiltersEnum.startedFrom, + VoucherListUrlFiltersEnum.startedTo + ); case VoucherFilterKeys.timesUsed: - if (!active) { - return { - timesUsedFrom: undefined, - timesUsedTo: undefined - }; - } - if (multiple) { - return { - timesUsedFrom: value[0], - timesUsedTo: value[1] - }; - } - - return { - timesUsedFrom: value[0], - timesUsedTo: value[0] - }; + return getMinMaxQueryParam( + filter, + VoucherListUrlFiltersEnum.timesUsedFrom, + VoucherListUrlFiltersEnum.timesUsedTo + ); case VoucherFilterKeys.status: - if (!active) { - return { - status: undefined - }; - } - return { - status: value.map(val => findValueInEnum(val, DiscountStatusEnum)) - }; + return getMultipleEnumValueQueryParam( + filter, + VoucherListUrlFiltersWithMultipleValues.status, + DiscountStatusEnum + ); } } diff --git a/src/orders/views/OrderDraftList/filters.ts b/src/orders/views/OrderDraftList/filters.ts index ce1267c0c..a6814a4e0 100644 --- a/src/orders/views/OrderDraftList/filters.ts +++ b/src/orders/views/OrderDraftList/filters.ts @@ -13,7 +13,9 @@ import { import { createFilterTabUtils, createFilterUtils, - getGteLteVariables + getGteLteVariables, + getMinMaxQueryParam, + getSingleValueQueryParam } from "../../../utils/filters"; export const ORDER_DRAFT_FILTERS_KEY = "orderDraftFilters"; @@ -58,37 +60,21 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): OrderDraftListUrlFilters { - const { active, multiple, name, value } = filter; + const { name } = filter; switch (name) { case OrderDraftFilterKeys.created: - if (!active) { - return { - createdFrom: undefined, - createdTo: undefined - }; - } - if (multiple) { - return { - createdFrom: value[0], - createdTo: value[1] - }; - } - - return { - createdFrom: value[0], - createdTo: value[0] - }; + return getMinMaxQueryParam( + filter, + OrderDraftListUrlFiltersEnum.createdFrom, + OrderDraftListUrlFiltersEnum.createdTo + ); case OrderDraftFilterKeys.customer: - if (!active) { - return { - customer: undefined - }; - } - return { - customer: value[0] - }; + return getSingleValueQueryParam( + filter, + OrderDraftListUrlFiltersEnum.customer + ); } } diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index d4dc874f1..db3bfec82 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -13,7 +13,9 @@ import { createFilterTabUtils, createFilterUtils, dedupeFilter, - getGteLteVariables + getGteLteVariables, + getMinMaxQueryParam, + getMultipleEnumValueQueryParam } from "../../../utils/filters"; import { OrderListUrlFilters, @@ -75,37 +77,22 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): OrderListUrlFilters { - const { active, multiple, name, value } = filter; + const { name } = filter; switch (name) { case OrderFilterKeys.created: - if (!active) { - return { - createdFrom: undefined, - createdTo: undefined - }; - } - if (multiple) { - return { - createdFrom: value[0], - createdTo: value[1] - }; - } - - return { - createdFrom: value[0], - createdTo: value[0] - }; + return getMinMaxQueryParam( + filter, + OrderListUrlFiltersEnum.createdFrom, + OrderListUrlFiltersEnum.createdTo + ); case OrderFilterKeys.status: - if (!active) { - return { - status: undefined - }; - } - return { - status: value.map(val => findInEnum(val, OrderStatus)) - }; + return getMultipleEnumValueQueryParam( + filter, + OrderListUrlFiltersWithMultipleValuesEnum.status, + OrderStatus + ); } } diff --git a/src/plugins/views/PluginList/filters.ts b/src/plugins/views/PluginList/filters.ts index 0a995fffa..ae33062e3 100644 --- a/src/plugins/views/PluginList/filters.ts +++ b/src/plugins/views/PluginList/filters.ts @@ -12,7 +12,8 @@ import { } from "../../urls"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getSingleValueQueryParam } from "../../../utils/filters"; export const PLUGIN_FILTERS_KEY = "pluginFilters"; @@ -44,18 +45,11 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): PluginListUrlFilters { - const { active, name, value } = filter; + const { name } = filter; switch (name) { case PluginFilterKeys.active: - if (!active) { - return { - active: undefined - }; - } - return { - active: value[0] - }; + return getSingleValueQueryParam(filter, PluginListUrlFiltersEnum.active); } } diff --git a/src/productTypes/views/ProductTypeList/filters.ts b/src/productTypes/views/ProductTypeList/filters.ts index 9655679ee..c21c4e426 100644 --- a/src/productTypes/views/ProductTypeList/filters.ts +++ b/src/productTypes/views/ProductTypeList/filters.ts @@ -11,7 +11,8 @@ import { } from "@saleor/productTypes/components/ProductTypeListPage"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getSingleValueQueryParam } from "../../../utils/filters"; import { ProductTypeListUrlFilters, @@ -55,30 +56,20 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): ProductTypeListUrlFilters { - const { active, name, value } = filter; + const { name } = filter; switch (name) { case ProductTypeFilterKeys.configurable: - if (!active) { - return { - configurable: undefined - }; - } - - return { - configurable: value[0] - }; + return getSingleValueQueryParam( + filter, + ProductTypeListUrlFiltersEnum.configurable + ); case ProductTypeFilterKeys.type: - if (!active) { - return { - type: undefined - }; - } - - return { - type: value[0] - }; + return getSingleValueQueryParam( + filter, + ProductTypeListUrlFiltersEnum.type + ); } } diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 1b94e5b62..aad3278a4 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -12,7 +12,9 @@ import { import { createFilterTabUtils, createFilterUtils, - getGteLteVariables + getGteLteVariables, + getMinMaxQueryParam, + getSingleEnumValueQueryParam } from "../../../utils/filters"; import { ProductListUrlFilters, @@ -71,47 +73,29 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): ProductListUrlFilters { - const { active, multiple, name, value } = filter; + const { name } = filter; switch (name) { case ProductFilterKeys.price: - if (!active) { - return { - priceFrom: undefined, - priceTo: undefined - }; - } - if (multiple) { - return { - priceFrom: value[0], - priceTo: value[1] - }; - } - - return { - priceFrom: value[0], - priceTo: value[0] - }; + return getMinMaxQueryParam( + filter, + ProductListUrlFiltersEnum.priceFrom, + ProductListUrlFiltersEnum.priceTo + ); case ProductFilterKeys.status: - if (!active) { - return { - status: undefined - }; - } - return { - status: findValueInEnum(value[0], ProductStatus) - }; + return getSingleEnumValueQueryParam( + filter, + ProductListUrlFiltersEnum.status, + ProductStatus + ); case ProductFilterKeys.stock: - if (!active) { - return { - stockStatus: undefined - }; - } - return { - stockStatus: findValueInEnum(value[0], StockAvailability) - }; + return getSingleEnumValueQueryParam( + filter, + ProductListUrlFiltersEnum.stockStatus, + StockAvailability + ); } } diff --git a/src/services/views/ServiceList/filters.ts b/src/services/views/ServiceList/filters.ts index 36fe240a2..f8eaddd18 100644 --- a/src/services/views/ServiceList/filters.ts +++ b/src/services/views/ServiceList/filters.ts @@ -12,7 +12,8 @@ import { } from "../../urls"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getSingleValueQueryParam } from "../../../utils/filters"; export const STAFF_FILTERS_KEY = "staffFilters"; @@ -44,18 +45,11 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): ServiceListUrlFilters { - const { active, name, value } = filter; + const { name } = filter; switch (name) { case ServiceFilterKeys.active: - if (!active) { - return { - active: undefined - }; - } - return { - active: value[0] - }; + return getSingleValueQueryParam(filter, ServiceListUrlFiltersEnum.active); } } diff --git a/src/staff/views/StaffList/filters.ts b/src/staff/views/StaffList/filters.ts index c26bf5723..cfb46d078 100644 --- a/src/staff/views/StaffList/filters.ts +++ b/src/staff/views/StaffList/filters.ts @@ -7,7 +7,8 @@ import { } from "@saleor/staff/components/StaffListPage"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + getSingleEnumValueQueryParam } from "../../../utils/filters"; import { StaffListUrlFilters, @@ -42,19 +43,15 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): StaffListUrlFilters { - const { active, name, value } = filter; + const { name } = filter; switch (name) { case StaffFilterKeys.status: - if (!active) { - return { - status: undefined - }; - } - - return { - status: value[0] - }; + return getSingleEnumValueQueryParam( + filter, + StaffListUrlFiltersEnum.status, + StaffMemberStatus + ); } } diff --git a/src/utils/filters/filters.ts b/src/utils/filters/filters.ts index 00d38887f..f2e42ae85 100644 --- a/src/utils/filters/filters.ts +++ b/src/utils/filters/filters.ts @@ -1,4 +1,5 @@ import { IFilterElement, IFilter } from "@saleor/components/Filter"; +import { findValueInEnum } from "@saleor/misc"; function createFilterUtils< TQueryParams extends object, @@ -56,4 +57,83 @@ export function getGteLteVariables(variables: GteLte): GteLte | null { return null; } +export function getSingleValueQueryParam< + TKey extends string, + TUrlKey extends string +>(param: IFilterElement, key: TUrlKey) { + const { active, value } = param; + + if (!active) { + return { + [key]: undefined + }; + } + + return { + [key]: value[0] + }; +} + +export function getSingleEnumValueQueryParam< + TKey extends string, + TUrlKey extends string, + TEnum extends object +>(param: IFilterElement, key: TUrlKey, haystack: TEnum) { + const { active, value } = param; + + if (!active) { + return { + [key]: undefined + }; + } + + return { + [key]: findValueInEnum(value[0], haystack) + }; +} + +export function getMultipleEnumValueQueryParam< + TKey extends string, + TUrlKey extends string, + TEnum extends object +>(param: IFilterElement, key: TUrlKey, haystack: TEnum) { + const { active, value } = param; + + if (!active) { + return { + [key]: undefined + }; + } + + return { + [key]: value.map(val => findValueInEnum(val, haystack)) + }; +} + +export function getMinMaxQueryParam< + TKey extends string, + TUrlKey extends string +>(param: IFilterElement, keyFrom: TUrlKey, keyTo: TUrlKey) { + const { active, multiple, value } = param; + + if (!active) { + return { + [keyFrom]: undefined, + [keyTo]: undefined + }; + } + + if (multiple) { + return { + [keyFrom]: value[0], + [keyTo]: value[1] + }; + } + + return { + [keyFrom]: value[0], + [keyTo]: value[0] + }; +} + export default createFilterUtils; diff --git a/src/webhooks/views/WebhookList/filters.ts b/src/webhooks/views/WebhookList/filters.ts index 6dff2e242..d8026e2f4 100644 --- a/src/webhooks/views/WebhookList/filters.ts +++ b/src/webhooks/views/WebhookList/filters.ts @@ -1,5 +1,9 @@ import { WebhookFilterInput } from "@saleor/types/globalTypes"; -import { createFilterTabUtils, createFilterUtils } from "@saleor/utils/filters"; +import { + createFilterTabUtils, + createFilterUtils, + getSingleValueQueryParam +} from "@saleor/utils/filters"; import { IFilterElement } from "@saleor/components/Filter"; import { WebhookListFilterOpts, @@ -40,18 +44,11 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): WebhookListUrlFilters { - const { active, name, value } = filter; + const { name } = filter; switch (name) { case WebhookFilterKeys.isActive: - if (!active) { - return { - active: undefined - }; - } - return { - active: value[0] - }; + return getSingleValueQueryParam(filter, WebhookListUrlFiltersEnum.active); } } From 525ce272e32df11f0b9182164ee0381980232da3 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 15 Jan 2020 12:49:47 +0100 Subject: [PATCH 65/85] Add debug hook --- src/hooks/debug/useOnMount.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/hooks/debug/useOnMount.ts diff --git a/src/hooks/debug/useOnMount.ts b/src/hooks/debug/useOnMount.ts new file mode 100644 index 000000000..48bb57f11 --- /dev/null +++ b/src/hooks/debug/useOnMount.ts @@ -0,0 +1,8 @@ +import { useEffect } from "react"; + +function useOnMount(name: string) { + // eslint-disable-next-line no-console + useEffect(() => console.log(`mounted node ${name}`), []); +} + +export default useOnMount; From aeb209744a9b415544b0265e84ee7b29687a173c Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 15 Jan 2020 16:36:45 +0100 Subject: [PATCH 66/85] Add category and collection filter --- src/components/Filter/FilterContent.tsx | 65 ++++++++++++ src/components/Filter/reducer.ts | 31 +++++- src/components/Filter/types.ts | 8 +- src/components/Filter/useFilter.ts | 12 ++- .../SingleAutocompleteSelectField.tsx | 21 ++-- .../components/VoucherListPage/filters.ts | 11 +- .../components/ProductListPage/filters.ts | 46 +++++++- src/products/queries.ts | 30 ++++++ .../types/InitialProductFilterData.ts | 49 +++++++++ src/products/urls.ts | 10 +- .../views/ProductList/ProductList.tsx | 44 +++++++- .../__snapshots__/filters.test.ts.snap | 8 +- .../views/ProductList/filters.test.ts | 35 ++++++ src/products/views/ProductList/filters.ts | 100 +++++++++++++++++- src/types.ts | 8 ++ src/utils/filters/fields.ts | 24 ++++- src/utils/filters/filters.ts | 23 ++++ 17 files changed, 490 insertions(+), 35 deletions(-) create mode 100644 src/products/types/InitialProductFilterData.ts diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index d0eefc5a6..584336963 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -12,11 +12,16 @@ import { fade } from "@material-ui/core/styles/colorManipulator"; import { buttonMessages } from "@saleor/intl"; import { TextField } from "@material-ui/core"; import { toggle } from "@saleor/utils/lists"; +import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; +import useStateFromProps from "@saleor/hooks/useStateFromProps"; import Hr from "../Hr"; import Checkbox from "../Checkbox"; import SingleSelectField from "../SingleSelectField"; import { SingleAutocompleteChoiceType } from "../SingleAutocompleteSelectField"; import FormSpacer from "../FormSpacer"; +import MultiAutocompleteSelectField, { + MultiAutocompleteChoiceType +} from "../MultiAutocompleteSelectField"; import { IFilter, FieldType, FilterType } from "./types"; import Arrow from "./Arrow"; import { FilterReducerAction } from "./reducer"; @@ -107,6 +112,18 @@ const FilterContent: React.FC = ({ }) => { const intl = useIntl(); const classes = useStyles({}); + const [ + autocompleteDisplayValues, + setAutocompleteDisplayValues + ] = useStateFromProps>( + filters.reduce((acc, filterField) => { + if (filterField.type === FieldType.autocomplete) { + acc[filterField.name] = filterField.displayValues; + } + + return acc; + }, {}) + ); return ( @@ -409,6 +426,54 @@ const FilterContent: React.FC = ({ />
))} + {filterField.type === FieldType.autocomplete && + filterField.multiple && ( + + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: toggle( + event.target.value, + filterField.value, + (a, b) => a === b + ) + } + }, + type: "set-property" + }), + value => + setAutocompleteDisplayValues({ + ...autocompleteDisplayValues, + [filterField.name]: toggle( + value[0], + autocompleteDisplayValues[filterField.name], + (a, b) => a.value === b.value + ) + }), + [], + filterField.options + )} + fetchChoices={filterField.onSearchChange} + loading={filterField.loading} + data-tc={filterField.name} + key={filterField.name} + /> + )}
)} diff --git a/src/components/Filter/reducer.ts b/src/components/Filter/reducer.ts index a36a09050..33eb850db 100644 --- a/src/components/Filter/reducer.ts +++ b/src/components/Filter/reducer.ts @@ -1,16 +1,39 @@ import { update } from "@saleor/utils/lists"; import { IFilter, IFilterElementMutableData } from "./types"; -export type FilterReducerActionType = "clear" | "reset" | "set-property"; +export type FilterReducerActionType = + | "clear" + | "merge" + | "reset" + | "set-property"; export interface FilterReducerAction { type: FilterReducerActionType; payload: Partial<{ name: T; update: Partial; - reset: IFilter; + new: IFilter; }>; } +function merge( + prevState: IFilter, + newState: IFilter +): IFilter { + return newState.map(newFilter => { + const prevFilter = prevState.find( + prevFilter => prevFilter.name === newFilter.name + ); + if (!!prevFilter) { + return { + ...newFilter, + active: prevFilter.active + }; + } + + return newFilter; + }); +} + function setProperty( prevState: IFilter, filter: T, @@ -32,8 +55,10 @@ function reduceFilter( switch (action.type) { case "set-property": return setProperty(prevState, action.payload.name, action.payload.update); + case "merge": + return merge(prevState, action.payload.new); case "reset": - return action.payload.reset; + return action.payload.new; default: return prevState; diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index eea2f96d5..ea257c8f7 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -1,7 +1,8 @@ -import { FetchMoreProps } from "@saleor/types"; +import { FetchMoreProps, SearchPageProps } from "@saleor/types"; import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; export enum FieldType { + autocomplete, boolean, date, dateTime, @@ -18,9 +19,10 @@ export interface IFilterElementMutableData { value: T[]; } export interface IFilterElement - extends Partial, - IFilterElementMutableData { + extends IFilterElementMutableData, + Partial { autocomplete?: boolean; + displayValues?: MultiAutocompleteChoiceType[]; label: string; name: T; type: FieldType; diff --git a/src/components/Filter/useFilter.ts b/src/components/Filter/useFilter.ts index 0a9b5ccb3..e51bebe56 100644 --- a/src/components/Filter/useFilter.ts +++ b/src/components/Filter/useFilter.ts @@ -17,12 +17,20 @@ function useFilter(initialFilter: IFilter): UseFilter { const reset = () => dispatchFilterAction({ payload: { - reset: initialFilter + new: initialFilter }, type: "reset" }); - useEffect(reset, [initialFilter]); + const refresh = () => + dispatchFilterAction({ + payload: { + new: initialFilter + }, + type: "merge" + }); + + useEffect(refresh, [initialFilter]); return [data, dispatchFilterAction, reset]; } diff --git a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx index 902103756..48b6e0692 100644 --- a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx +++ b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx @@ -41,17 +41,14 @@ export interface SingleAutocompleteSelectFieldProps onChange: (event: React.ChangeEvent) => void; } -const DebounceAutocomplete: React.ComponentType< - DebounceProps -> = Debounce; +const DebounceAutocomplete: React.ComponentType> = Debounce; -const SingleAutocompleteSelectFieldComponent: React.FC< - SingleAutocompleteSelectFieldProps -> = props => { +const SingleAutocompleteSelectFieldComponent: React.FC = props => { const { - choices, - allowCustomValues, + choices, disabled, displayValue, emptyOption, @@ -169,9 +166,11 @@ const SingleAutocompleteSelectFieldComponent: React.FC< ); }; -const SingleAutocompleteSelectField: React.FC< - SingleAutocompleteSelectFieldProps -> = ({ choices, fetchChoices, ...rest }) => { +const SingleAutocompleteSelectField: React.FC = ({ + choices, + fetchChoices, + ...rest +}) => { const [query, setQuery] = React.useState(""); if (fetchChoices) { return ( diff --git a/src/discounts/components/VoucherListPage/filters.ts b/src/discounts/components/VoucherListPage/filters.ts index 4faa735cd..e1c2c56a5 100644 --- a/src/discounts/components/VoucherListPage/filters.ts +++ b/src/discounts/components/VoucherListPage/filters.ts @@ -7,8 +7,7 @@ import { } from "@saleor/utils/filters/fields"; import { VoucherDiscountType, - DiscountStatusEnum, - DiscountValueTypeEnum + DiscountStatusEnum } from "@saleor/types/globalTypes"; import { MinMax, FilterOpts } from "@saleor/types"; import { IFilter } from "@saleor/components/Filter"; @@ -118,11 +117,15 @@ export function createFilterStructure( [ { label: intl.formatMessage(messages.fixed), - value: DiscountValueTypeEnum.FIXED + value: VoucherDiscountType.FIXED }, { label: intl.formatMessage(messages.percentage), - value: DiscountValueTypeEnum.PERCENTAGE + value: VoucherDiscountType.PERCENTAGE + }, + { + label: intl.formatMessage(messages.percentage), + value: VoucherDiscountType.SHIPPING } ] ), diff --git a/src/products/components/ProductListPage/filters.ts b/src/products/components/ProductListPage/filters.ts index 3f65c2211..70205b500 100644 --- a/src/products/components/ProductListPage/filters.ts +++ b/src/products/components/ProductListPage/filters.ts @@ -1,20 +1,26 @@ import { defineMessages, IntlShape } from "react-intl"; -import { FilterOpts, MinMax } from "@saleor/types"; +import { FilterOpts, MinMax, AutocompleteFilterOpts } from "@saleor/types"; import { StockAvailability } from "@saleor/types/globalTypes"; import { createOptionsField, - createPriceField + createPriceField, + createAutocompleteField } from "@saleor/utils/filters/fields"; import { IFilter } from "@saleor/components/Filter"; +import { sectionNames } from "@saleor/intl"; export enum ProductFilterKeys { + categories = "categories", + collections = "collections", status = "status", price = "price", stock = "stock" } export interface ProductListFilterOpts { + categories: FilterOpts & AutocompleteFilterOpts; + collections: FilterOpts & AutocompleteFilterOpts; price: FilterOpts; status: FilterOpts; stockStatus: FilterOpts; @@ -105,6 +111,42 @@ export function createFilterStructure( opts.price.value ), active: opts.price.active + }, + { + ...createAutocompleteField( + ProductFilterKeys.categories, + intl.formatMessage(sectionNames.categories), + opts.categories.value, + opts.categories.displayValues, + true, + opts.categories.choices, + { + hasMore: opts.categories.hasMore, + initialSearch: "", + loading: opts.categories.loading, + onFetchMore: opts.categories.onFetchMore, + onSearchChange: opts.categories.onSearchChange + } + ), + active: opts.categories.active + }, + { + ...createAutocompleteField( + ProductFilterKeys.collections, + intl.formatMessage(sectionNames.collections), + opts.collections.value, + opts.collections.displayValues, + true, + opts.collections.choices, + { + hasMore: opts.collections.hasMore, + initialSearch: "", + loading: opts.collections.loading, + onFetchMore: opts.collections.onFetchMore, + onSearchChange: opts.collections.onSearchChange + } + ), + active: opts.collections.active } ]; } diff --git a/src/products/queries.ts b/src/products/queries.ts index c8e27af29..513ec26b0 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { pageInfoFragment, TypedQuery } from "../queries"; import { AvailableInGridAttributes, @@ -22,6 +23,10 @@ import { ProductVariantDetails, ProductVariantDetailsVariables } from "./types/ProductVariantDetails"; +import { + InitialProductFilterData, + InitialProductFilterDataVariables +} from "./types/InitialProductFilterData"; export const fragmentMoney = gql` fragment Money on Money { @@ -209,6 +214,31 @@ export const fragmentVariant = gql` } `; +const initialProductFilterDataQuery = gql` + query InitialProductFilterData($categories: [ID!], $collections: [ID!]) { + categories(first: 20, filter: { ids: $categories }) { + edges { + node { + id + name + } + } + } + collections(first: 20, filter: { ids: $collections }) { + edges { + node { + id + name + } + } + } + } +`; +export const useInitialProductFilterDataQuery = makeQuery< + InitialProductFilterData, + InitialProductFilterDataVariables +>(initialProductFilterDataQuery); + const productListQuery = gql` ${productFragment} query ProductList( diff --git a/src/products/types/InitialProductFilterData.ts b/src/products/types/InitialProductFilterData.ts new file mode 100644 index 000000000..58d6b9e29 --- /dev/null +++ b/src/products/types/InitialProductFilterData.ts @@ -0,0 +1,49 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: InitialProductFilterData +// ==================================================== + +export interface InitialProductFilterData_categories_edges_node { + __typename: "Category"; + id: string; + name: string; +} + +export interface InitialProductFilterData_categories_edges { + __typename: "CategoryCountableEdge"; + node: InitialProductFilterData_categories_edges_node; +} + +export interface InitialProductFilterData_categories { + __typename: "CategoryCountableConnection"; + edges: InitialProductFilterData_categories_edges[]; +} + +export interface InitialProductFilterData_collections_edges_node { + __typename: "Collection"; + id: string; + name: string; +} + +export interface InitialProductFilterData_collections_edges { + __typename: "CollectionCountableEdge"; + node: InitialProductFilterData_collections_edges_node; +} + +export interface InitialProductFilterData_collections { + __typename: "CollectionCountableConnection"; + edges: InitialProductFilterData_collections_edges[]; +} + +export interface InitialProductFilterData { + categories: InitialProductFilterData_categories | null; + collections: InitialProductFilterData_collections | null; +} + +export interface InitialProductFilterDataVariables { + categories?: string[] | null; + collections?: string[] | null; +} diff --git a/src/products/urls.ts b/src/products/urls.ts index 426471fd7..ac5a946a9 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -9,7 +9,8 @@ import { Filters, Pagination, Sort, - TabActionDialog + TabActionDialog, + FiltersWithMultipleValues } from "../types"; const productSection = "/products/"; @@ -30,7 +31,12 @@ export enum ProductListUrlFiltersEnum { stockStatus = "stockStatus", query = "query" } -export type ProductListUrlFilters = Filters; +export enum ProductListUrlFiltersWithMultipleValues { + categories = "categories", + collections = "collections" +} +export type ProductListUrlFilters = Filters & + FiltersWithMultipleValues; export enum ProductListUrlSortField { attribute = "attribute", name = "name", diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 7eb034d10..a84ee4d30 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -10,7 +10,11 @@ import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; import SaveFilterTabDialog, { SaveFilterTabDialogFormData } from "@saleor/components/SaveFilterTabDialog"; -import { defaultListSettings, ProductListColumns } from "@saleor/config"; +import { + defaultListSettings, + ProductListColumns, + DEFAULT_INITIAL_SEARCH_DATA +} from "@saleor/config"; import useBulkActions from "@saleor/hooks/useBulkActions"; import useListSettings from "@saleor/hooks/useListSettings"; import useNavigator from "@saleor/hooks/useNavigator"; @@ -26,6 +30,8 @@ import { ListViews } from "@saleor/types"; import { getSortUrlVariables } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; +import useCategorySearch from "@saleor/searches/useCategorySearch"; +import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import ProductListPage from "../../components/ProductListPage"; import { TypedProductBulkDeleteMutation, @@ -33,7 +39,8 @@ import { } from "../../mutations"; import { AvailableInGridAttributesQuery, - TypedProductListQuery + TypedProductListQuery, + useInitialProductFilterDataQuery } from "../../queries"; import { productBulkDelete } from "../../types/productBulkDelete"; import { productBulkPublish } from "../../types/productBulkPublish"; @@ -73,6 +80,19 @@ export const ProductList: React.FC = ({ params }) => { ListViews.PRODUCT_LIST ); const intl = useIntl(); + const { data: initialFilterData } = useInitialProductFilterDataQuery({ + skip: !(!!params.categories || !!params.collections), + variables: { + categories: params.categories, + collections: params.collections + } + }); + const searchCategories = useCategorySearch({ + variables: DEFAULT_INITIAL_SEARCH_DATA + }); + const searchCollections = useCollectionSearch({ + variables: DEFAULT_INITIAL_SEARCH_DATA + }); React.useEffect( () => @@ -156,6 +176,24 @@ export const ProductList: React.FC = ({ params }) => { [params, settings.rowNumber] ); + const filterOpts = getFilterOpts( + params, + { + initial: maybe( + () => initialFilterData.categories.edges.map(edge => edge.node), + [] + ), + search: searchCategories + }, + { + initial: maybe( + () => initialFilterData.collections.edges.map(edge => edge.node), + [] + ), + search: searchCollections + } + ); + return ( = ({ params }) => { defaultSettings={ defaultListSettings[ListViews.PRODUCT_LIST] } - filterOpts={getFilterOpts(params)} + filterOpts={filterOpts} gridAttributes={maybe( () => attributes.data.grid.edges.map(edge => edge.node), diff --git a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap index cb6472a86..07f2f3bab 100644 --- a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap +++ b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap @@ -2,6 +2,12 @@ exports[`Filtering URL params should not be empty if active filters are present 1`] = ` Object { + "categories": Array [ + "878752", + ], + "collections": Array [ + "Q29sbGVjdGlvbjoc", + ], "priceFrom": "10", "priceTo": "20", "status": "published", @@ -9,4 +15,4 @@ Object { } `; -exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20"`; +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B0%5D=878752&collections%5B0%5D=Q29sbGVjdGlvbjoc"`; diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts index 43b4f23ab..300a2ab15 100644 --- a/src/products/views/ProductList/filters.test.ts +++ b/src/products/views/ProductList/filters.test.ts @@ -10,6 +10,9 @@ import { getFilterQueryParams } from "@saleor/utils/filters"; import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; import { config } from "@test/intl"; import { StockAvailability } from "@saleor/types/globalTypes"; +import { categories } from "@saleor/categories/fixtures"; +import { fetchMoreProps, searchPageProps } from "@saleor/fixtures"; +import { collections } from "@saleor/collections/fixtures"; import { getFilterVariables, getFilterQueryParam } from "./filters"; describe("Filtering query params", () => { @@ -37,6 +40,38 @@ describe("Filtering URL params", () => { const intl = createIntl(config); const filters = createFilterStructure(intl, { + categories: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + choices: categories.slice(5).map(category => ({ + label: category.name, + value: category.id + })), + displayValues: [ + { + label: categories[5].name, + value: categories[5].id + } + ], + value: [categories[5].id] + }, + collections: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + choices: collections.slice(5).map(category => ({ + label: category.name, + value: category.id + })), + displayValues: [ + { + label: collections[5].name, + value: collections[5].id + } + ], + value: [collections[5].id] + }, price: { active: false, value: { diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index aad3278a4..7860fe536 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -4,6 +4,19 @@ import { ProductListFilterOpts, ProductStatus } from "@saleor/products/components/ProductListPage"; +import { UseSearchResult } from "@saleor/hooks/makeSearch"; +import { + SearchCategories, + SearchCategoriesVariables +} from "@saleor/searches/types/SearchCategories"; +import { + InitialProductFilterData_categories_edges_node, + InitialProductFilterData_collections_edges_node +} from "@saleor/products/types/InitialProductFilterData"; +import { + SearchCollections, + SearchCollectionsVariables +} from "@saleor/searches/types/SearchCollections"; import { IFilterElement } from "../../../components/Filter"; import { ProductFilterInput, @@ -14,20 +27,87 @@ import { createFilterUtils, getGteLteVariables, getMinMaxQueryParam, - getSingleEnumValueQueryParam + getSingleEnumValueQueryParam, + dedupeFilter, + getMultipleValueQueryParam } from "../../../utils/filters"; import { ProductListUrlFilters, ProductListUrlFiltersEnum, - ProductListUrlQueryParams + ProductListUrlQueryParams, + ProductListUrlFiltersWithMultipleValues } from "../../urls"; export const PRODUCT_FILTERS_KEY = "productFilters"; export function getFilterOpts( - params: ProductListUrlFilters + params: ProductListUrlFilters, + categories: { + initial: InitialProductFilterData_categories_edges_node[]; + search: UseSearchResult; + }, + collections: { + initial: InitialProductFilterData_collections_edges_node[]; + search: UseSearchResult; + } ): ProductListFilterOpts { return { + categories: { + active: !!params.categories, + choices: maybe( + () => + categories.search.result.data.search.edges.map(edge => ({ + label: edge.node.name, + value: edge.node.id + })), + [] + ), + displayValues: maybe( + () => + categories.initial.map(category => ({ + label: category.name, + value: category.id + })), + [] + ), + hasMore: maybe( + () => categories.search.result.data.search.pageInfo.hasNextPage, + false + ), + initialSearch: "", + loading: categories.search.result.loading, + onFetchMore: categories.search.loadMore, + onSearchChange: categories.search.search, + value: maybe(() => dedupeFilter(params.categories), []) + }, + collections: { + active: !!params.collections, + choices: maybe( + () => + collections.search.result.data.search.edges.map(edge => ({ + label: edge.node.name, + value: edge.node.id + })), + [] + ), + displayValues: maybe( + () => + collections.initial.map(category => ({ + label: category.name, + value: category.id + })), + [] + ), + hasMore: maybe( + () => collections.search.result.data.search.pageInfo.hasNextPage, + false + ), + initialSearch: "", + loading: collections.search.result.loading, + onFetchMore: collections.search.loadMore, + onSearchChange: collections.search.search, + value: maybe(() => dedupeFilter(params.collections), []) + }, price: { active: maybe( () => @@ -54,6 +134,8 @@ export function getFilterVariables( params: ProductListUrlFilters ): ProductFilterInput { return { + categories: params.categories !== undefined ? params.categories : null, + collections: params.collections !== undefined ? params.collections : null, isPublished: params.status !== undefined ? params.status === ProductStatus.PUBLISHED @@ -76,6 +158,18 @@ export function getFilterQueryParam( const { name } = filter; switch (name) { + case ProductFilterKeys.categories: + return getMultipleValueQueryParam( + filter, + ProductListUrlFiltersWithMultipleValues.categories + ); + + case ProductFilterKeys.collections: + return getMultipleValueQueryParam( + filter, + ProductListUrlFiltersWithMultipleValues.collections + ); + case ProductFilterKeys.price: return getMinMaxQueryParam( filter, diff --git a/src/types.ts b/src/types.ts index dc2011c19..ec663ad21 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import { MutationResult } from "react-apollo"; import { User_permissions } from "./auth/types/User"; import { ConfirmButtonTransitionState } from "./components/ConfirmButton"; import { IFilter } from "./components/Filter"; +import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelectField"; export interface UserError { field: string; @@ -176,3 +177,10 @@ export interface FilterOpts { active: boolean; value: T; } + +export interface AutocompleteFilterOpts + extends FetchMoreProps, + SearchPageProps { + choices: MultiAutocompleteChoiceType[]; + displayValues: MultiAutocompleteChoiceType[]; +} diff --git a/src/utils/filters/fields.ts b/src/utils/filters/fields.ts index 69dae346b..a46473bbe 100644 --- a/src/utils/filters/fields.ts +++ b/src/utils/filters/fields.ts @@ -1,6 +1,6 @@ import { IFilterElement, FieldType } from "@saleor/components/Filter"; import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; -import { MinMax } from "@saleor/types"; +import { MinMax, FetchMoreProps, SearchPageProps } from "@saleor/types"; export function createPriceField( name: T, @@ -65,6 +65,28 @@ export function createOptionsField( }; } +export function createAutocompleteField( + name: T, + label: string, + defaultValue: string[], + displayValues: MultiAutocompleteChoiceType[], + multiple: boolean, + options: MultiAutocompleteChoiceType[], + opts: FetchMoreProps & SearchPageProps +): IFilterElement { + return { + ...opts, + active: false, + displayValues, + label, + multiple, + name, + options, + type: FieldType.autocomplete, + value: defaultValue + }; +} + export function createTextField( name: T, label: string, diff --git a/src/utils/filters/filters.ts b/src/utils/filters/filters.ts index f2e42ae85..c80edc459 100644 --- a/src/utils/filters/filters.ts +++ b/src/utils/filters/filters.ts @@ -1,3 +1,5 @@ +import isArray from "lodash-es/isArray"; + import { IFilterElement, IFilter } from "@saleor/components/Filter"; import { findValueInEnum } from "@saleor/misc"; @@ -25,6 +27,10 @@ function createFilterUtils< } export function dedupeFilter(array: T[]): T[] { + if (!isArray(array)) { + return [array]; + } + return Array.from(new Set(array)); } @@ -110,6 +116,23 @@ export function getMultipleEnumValueQueryParam< }; } +export function getMultipleValueQueryParam< + TKey extends string, + TUrlKey extends string +>(param: IFilterElement, key: TUrlKey) { + const { active, value } = param; + + if (!active) { + return { + [key]: undefined + }; + } + + return { + [key]: value + }; +} + export function getMinMaxQueryParam< TKey extends string, TUrlKey extends string From 15470221c229975ec75d239287d95bf922192075 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 16 Jan 2020 13:40:22 +0100 Subject: [PATCH 67/85] Add click away --- src/components/Filter/Filter.tsx | 106 ++++++++++++++++--------------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index 148f1175d..f3c1f96d1 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -1,4 +1,5 @@ import ButtonBase from "@material-ui/core/ButtonBase"; +import ClickAwayListener from "@material-ui/core/ClickAwayListener"; import Grow from "@material-ui/core/Grow"; import Popper from "@material-ui/core/Popper"; import { makeStyles } from "@material-ui/core/styles"; @@ -96,58 +97,61 @@ const Filter: React.FC = props => { const isFilterActive = menu.some(filterElement => filterElement.active); return ( -
- setFilterMenuOpened(!isFilterMenuOpened)} - > - - - - {isFilterActive && ( - <> - - - {menu.reduce( - (acc, filterElement) => acc + (filterElement.active ? 1 : 0), - 0 - )} - - - )} - - - {({ TransitionProps, placement }) => ( - - { - onFilterAdd(data); - setFilterMenuOpened(false); + setFilterMenuOpened(false)}> +
+ setFilterMenuOpened(!isFilterMenuOpened)} + > + + + + {isFilterActive && ( + <> + + + {menu.reduce( + (acc, filterElement) => acc + (filterElement.active ? 1 : 0), + 0 + )} + + + )} + + + {({ TransitionProps, placement }) => ( + - - )} - -
+ > + { + onFilterAdd(data); + setFilterMenuOpened(false); + }} + /> +
+ )} +
+
+ ); }; Filter.displayName = "Filter"; From 0a58c3a5e19cf0906c928be9c30449da4241817a Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 16 Jan 2020 14:44:16 +0100 Subject: [PATCH 68/85] Refactor multiple choice field widget --- .../Filter/FilterAutocompleteField.tsx | 145 ++++++++++++++++++ src/components/Filter/FilterContent.tsx | 96 ++++++------ src/components/Filter/reducer.ts | 3 +- src/components/Link.tsx | 2 +- 4 files changed, 200 insertions(+), 46 deletions(-) create mode 100644 src/components/Filter/FilterAutocompleteField.tsx diff --git a/src/components/Filter/FilterAutocompleteField.tsx b/src/components/Filter/FilterAutocompleteField.tsx new file mode 100644 index 000000000..e880d0da4 --- /dev/null +++ b/src/components/Filter/FilterAutocompleteField.tsx @@ -0,0 +1,145 @@ +import React from "react"; +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import TextField from "@material-ui/core/TextField"; +import makeStyles from "@material-ui/core/styles/makeStyles"; +import { FormattedMessage } from "react-intl"; + +import { toggle } from "@saleor/utils/lists"; +import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; +import Link from "../Link"; +import Checkbox from "../Checkbox"; +import Hr from "../Hr"; +import { IFilterElement } from "./types"; +import { FilterReducerAction } from "./reducer"; + +interface FilterAutocompleteFieldProps { + displayValues: Record; + filterField: IFilterElement; + setDisplayValues: ( + values: Record + ) => void; + onFilterPropertyChange: React.Dispatch>; +} + +const useStyles = makeStyles( + theme => ({ + hr: { + backgroundColor: theme.palette.primary.light, + margin: theme.spacing(1, 0) + }, + input: { + padding: "12px 0 9px 12px" + }, + inputContainer: { + marginBottom: theme.spacing(1) + }, + option: { + left: -theme.spacing(0.5), + position: "relative" + }, + showMore: { + display: "inline-block", + marginTop: theme.spacing(1) + } + }), + { name: "FilterAutocompleteField" } +); + +const FilterAutocompleteField: React.FC = ({ + displayValues, + filterField, + setDisplayValues, + onFilterPropertyChange +}) => { + const classes = useStyles({}); + + const fieldDisplayValues = displayValues[filterField.name]; + const availableOptions = filterField.options.filter(option => + fieldDisplayValues.every( + displayValue => displayValue.value !== option.value + ) + ); + const displayHr = !( + (fieldDisplayValues.length === 0 && availableOptions.length > 0) || + (availableOptions.length === 0 && fieldDisplayValues.length > 0) + ); + + const handleChange = (option: MultiAutocompleteChoiceType) => { + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: toggle(option.value, filterField.value, (a, b) => a === b) + } + }, + type: "set-property" + }); + + setDisplayValues({ + ...displayValues, + [filterField.name]: toggle( + option, + fieldDisplayValues, + (a, b) => a.value === b.value + ) + }); + }; + + return ( +
+ filterField.onSearchChange(event.target.value)} + /> + {fieldDisplayValues.map(displayValue => ( +
+ + } + label={displayValue.label} + name={filterField.name} + onChange={() => handleChange(displayValue)} + /> +
+ ))} + {displayHr &&
} + {availableOptions.map(option => ( +
+ + } + label={option.label} + name={filterField.name} + onChange={() => handleChange(option)} + /> +
+ ))} + {filterField.hasMore && ( + + + + )} +
+ ); +}; + +FilterAutocompleteField.displayName = "FilterAutocompleteField"; +export default FilterAutocompleteField; diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index 584336963..908329dfc 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -22,9 +22,11 @@ import FormSpacer from "../FormSpacer"; import MultiAutocompleteSelectField, { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; +import Link from "../Link"; import { IFilter, FieldType, FilterType } from "./types"; import Arrow from "./Arrow"; import { FilterReducerAction } from "./reducer"; +import FilterAutocompleteField from "./FilterAutocompleteField"; export interface FilterContentProps { currencySymbol: string; @@ -428,51 +430,57 @@ const FilterContent: React.FC = ({ ))} {filterField.type === FieldType.autocomplete && filterField.multiple && ( - - onFilterPropertyChange({ - payload: { - name: filterField.name, - update: { - value: toggle( - event.target.value, - filterField.value, - (a, b) => a === b - ) - } - }, - type: "set-property" - }), - value => - setAutocompleteDisplayValues({ - ...autocompleteDisplayValues, - [filterField.name]: toggle( - value[0], - autocompleteDisplayValues[filterField.name], - (a, b) => a.value === b.value - ) - }), - [], - filterField.options - )} - fetchChoices={filterField.onSearchChange} - loading={filterField.loading} - data-tc={filterField.name} - key={filterField.name} + + // + // onFilterPropertyChange({ + // payload: { + // name: filterField.name, + // update: { + // value: toggle( + // event.target.value, + // filterField.value, + // (a, b) => a === b + // ) + // } + // }, + // type: "set-property" + // }), + // value => + // setAutocompleteDisplayValues({ + // ...autocompleteDisplayValues, + // [filterField.name]: toggle( + // value[0], + // autocompleteDisplayValues[filterField.name], + // (a, b) => a.value === b.value + // ) + // }), + // [], + // filterField.options + // )} + // fetchChoices={filterField.onSearchChange} + // loading={filterField.loading} + // data-tc={filterField.name} + // key={filterField.name} + // /> )}
)} diff --git a/src/components/Filter/reducer.ts b/src/components/Filter/reducer.ts index 33eb850db..e8d837c7c 100644 --- a/src/components/Filter/reducer.ts +++ b/src/components/Filter/reducer.ts @@ -26,7 +26,8 @@ function merge( if (!!prevFilter) { return { ...newFilter, - active: prevFilter.active + active: prevFilter.active, + value: prevFilter.value }; } diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 3086f1bc5..3ddcec8a6 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -44,7 +44,7 @@ const Link: React.FC = props => { return ( Date: Thu, 16 Jan 2020 14:49:06 +0100 Subject: [PATCH 69/85] Add product type filter --- .../components/ProductListPage/filters.ts | 20 +++++ src/products/queries.ts | 18 +++- .../types/InitialProductFilterData.ts | 18 ++++ src/products/urls.ts | 3 +- .../views/ProductList/ProductList.tsx | 33 ++++++- src/products/views/ProductList/filters.ts | 88 +++++++++++++++---- 6 files changed, 154 insertions(+), 26 deletions(-) diff --git a/src/products/components/ProductListPage/filters.ts b/src/products/components/ProductListPage/filters.ts index 70205b500..1ac4bd5f7 100644 --- a/src/products/components/ProductListPage/filters.ts +++ b/src/products/components/ProductListPage/filters.ts @@ -15,6 +15,7 @@ export enum ProductFilterKeys { collections = "collections", status = "status", price = "price", + productType = "productType", stock = "stock" } @@ -22,6 +23,7 @@ export interface ProductListFilterOpts { categories: FilterOpts & AutocompleteFilterOpts; collections: FilterOpts & AutocompleteFilterOpts; price: FilterOpts; + productType: FilterOpts & AutocompleteFilterOpts; status: FilterOpts; stockStatus: FilterOpts; } @@ -147,6 +149,24 @@ export function createFilterStructure( } ), active: opts.collections.active + }, + { + ...createAutocompleteField( + ProductFilterKeys.productType, + intl.formatMessage(sectionNames.productTypes), + opts.productType.value, + opts.productType.displayValues, + true, + opts.productType.choices, + { + hasMore: opts.productType.hasMore, + initialSearch: "", + loading: opts.productType.loading, + onFetchMore: opts.productType.onFetchMore, + onSearchChange: opts.productType.onSearchChange + } + ), + active: opts.productType.active } ]; } diff --git a/src/products/queries.ts b/src/products/queries.ts index 513ec26b0..ca5c1a273 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -215,8 +215,12 @@ export const fragmentVariant = gql` `; const initialProductFilterDataQuery = gql` - query InitialProductFilterData($categories: [ID!], $collections: [ID!]) { - categories(first: 20, filter: { ids: $categories }) { + query InitialProductFilterData( + $categories: [ID!] + $collections: [ID!] + $productTypes: [ID!] + ) { + categories(first: 100, filter: { ids: $categories }) { edges { node { id @@ -224,7 +228,15 @@ const initialProductFilterDataQuery = gql` } } } - collections(first: 20, filter: { ids: $collections }) { + collections(first: 100, filter: { ids: $collections }) { + edges { + node { + id + name + } + } + } + productTypes(first: 100, filter: { ids: $productTypes }) { edges { node { id diff --git a/src/products/types/InitialProductFilterData.ts b/src/products/types/InitialProductFilterData.ts index 58d6b9e29..c4c323c0d 100644 --- a/src/products/types/InitialProductFilterData.ts +++ b/src/products/types/InitialProductFilterData.ts @@ -38,12 +38,30 @@ export interface InitialProductFilterData_collections { edges: InitialProductFilterData_collections_edges[]; } +export interface InitialProductFilterData_productTypes_edges_node { + __typename: "ProductType"; + id: string; + name: string; +} + +export interface InitialProductFilterData_productTypes_edges { + __typename: "ProductTypeCountableEdge"; + node: InitialProductFilterData_productTypes_edges_node; +} + +export interface InitialProductFilterData_productTypes { + __typename: "ProductTypeCountableConnection"; + edges: InitialProductFilterData_productTypes_edges[]; +} + export interface InitialProductFilterData { categories: InitialProductFilterData_categories | null; collections: InitialProductFilterData_collections | null; + productTypes: InitialProductFilterData_productTypes | null; } export interface InitialProductFilterDataVariables { categories?: string[] | null; collections?: string[] | null; + productTypes?: string[] | null; } diff --git a/src/products/urls.ts b/src/products/urls.ts index ac5a946a9..0f56302ea 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -33,7 +33,8 @@ export enum ProductListUrlFiltersEnum { } export enum ProductListUrlFiltersWithMultipleValues { categories = "categories", - collections = "collections" + collections = "collections", + productTypes = "productTypes" } export type ProductListUrlFilters = Filters & FiltersWithMultipleValues; diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index a84ee4d30..5d735a38c 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -32,6 +32,7 @@ import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandl import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import useCategorySearch from "@saleor/searches/useCategorySearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch"; +import useProductTypeSearch from "@saleor/searches/useProductTypeSearch"; import ProductListPage from "../../components/ProductListPage"; import { TypedProductBulkDeleteMutation, @@ -81,17 +82,34 @@ export const ProductList: React.FC = ({ params }) => { ); const intl = useIntl(); const { data: initialFilterData } = useInitialProductFilterDataQuery({ - skip: !(!!params.categories || !!params.collections), + skip: !( + !!params.categories || + !!params.collections || + !!params.productTypes + ), variables: { categories: params.categories, - collections: params.collections + collections: params.collections, + productTypes: params.productTypes } }); const searchCategories = useCategorySearch({ - variables: DEFAULT_INITIAL_SEARCH_DATA + variables: { + ...DEFAULT_INITIAL_SEARCH_DATA, + first: 5 + } }); const searchCollections = useCollectionSearch({ - variables: DEFAULT_INITIAL_SEARCH_DATA + variables: { + ...DEFAULT_INITIAL_SEARCH_DATA, + first: 5 + } + }); + const searchProductTypes = useProductTypeSearch({ + variables: { + ...DEFAULT_INITIAL_SEARCH_DATA, + first: 5 + } }); React.useEffect( @@ -191,6 +209,13 @@ export const ProductList: React.FC = ({ params }) => { [] ), search: searchCollections + }, + { + initial: maybe( + () => initialFilterData.productTypes.edges.map(edge => edge.node), + [] + ), + search: searchProductTypes } ); diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 7860fe536..02d0f4725 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -11,12 +11,17 @@ import { } from "@saleor/searches/types/SearchCategories"; import { InitialProductFilterData_categories_edges_node, - InitialProductFilterData_collections_edges_node + InitialProductFilterData_collections_edges_node, + InitialProductFilterData_productTypes_edges_node } from "@saleor/products/types/InitialProductFilterData"; import { SearchCollections, SearchCollectionsVariables } from "@saleor/searches/types/SearchCollections"; +import { + SearchProductTypes, + SearchProductTypesVariables +} from "@saleor/searches/types/SearchProductTypes"; import { IFilterElement } from "../../../components/Filter"; import { ProductFilterInput, @@ -29,7 +34,8 @@ import { getMinMaxQueryParam, getSingleEnumValueQueryParam, dedupeFilter, - getMultipleValueQueryParam + getMultipleValueQueryParam, + getSingleValueQueryParam } from "../../../utils/filters"; import { ProductListUrlFilters, @@ -49,6 +55,10 @@ export function getFilterOpts( collections: { initial: InitialProductFilterData_collections_edges_node[]; search: UseSearchResult; + }, + productTypes: { + initial: InitialProductFilterData_productTypes_edges_node[]; + search: UseSearchResult; } ): ProductListFilterOpts { return { @@ -62,14 +72,16 @@ export function getFilterOpts( })), [] ), - displayValues: maybe( - () => - categories.initial.map(category => ({ - label: category.name, - value: category.id - })), - [] - ), + displayValues: !!params.categories + ? maybe( + () => + categories.initial.map(category => ({ + label: category.name, + value: category.id + })), + [] + ) + : [], hasMore: maybe( () => categories.search.result.data.search.pageInfo.hasNextPage, false @@ -90,14 +102,16 @@ export function getFilterOpts( })), [] ), - displayValues: maybe( - () => - collections.initial.map(category => ({ - label: category.name, - value: category.id - })), - [] - ), + displayValues: !!params.collections + ? maybe( + () => + collections.initial.map(category => ({ + label: category.name, + value: category.id + })), + [] + ) + : undefined, hasMore: maybe( () => collections.search.result.data.search.pageInfo.hasNextPage, false @@ -119,6 +133,36 @@ export function getFilterOpts( min: maybe(() => params.priceFrom, "0") } }, + productType: { + active: !!params.productTypes, + choices: maybe( + () => + productTypes.search.result.data.search.edges.map(edge => ({ + label: edge.node.name, + value: edge.node.id + })), + [] + ), + displayValues: !!params.productTypes + ? maybe( + () => + productTypes.initial.map(productType => ({ + label: productType.name, + value: productType.id + })), + [] + ) + : [], + hasMore: maybe( + () => productTypes.search.result.data.search.pageInfo.hasNextPage, + false + ), + initialSearch: "", + loading: productTypes.search.result.loading, + onFetchMore: productTypes.search.loadMore, + onSearchChange: productTypes.search.search, + value: maybe(() => dedupeFilter(params.productTypes), []) + }, status: { active: maybe(() => params.status !== undefined, false), value: maybe(() => findValueInEnum(params.status, ProductStatus)) @@ -144,6 +188,8 @@ export function getFilterVariables( gte: parseFloat(params.priceFrom), lte: parseFloat(params.priceTo) }), + productType: + params.productTypes !== undefined ? params.productTypes[0] : null, search: params.query, stockAvailability: params.stockStatus !== undefined @@ -177,6 +223,12 @@ export function getFilterQueryParam( ProductListUrlFiltersEnum.priceTo ); + case ProductFilterKeys.productType: + return getMultipleValueQueryParam( + filter, + ProductListUrlFiltersWithMultipleValues.productTypes + ); + case ProductFilterKeys.status: return getSingleEnumValueQueryParam( filter, From 0d88bd4f4a158c87704446dbeef293515fc7a4bc Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 16 Jan 2020 15:03:24 +0100 Subject: [PATCH 70/85] Fix unreachable controls --- src/components/AppLayout/AppLayout.tsx | 1 - src/components/SaveButtonBar/SaveButtonBar.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index 32517e68e..1db6cb01c 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -47,7 +47,6 @@ const useStyles = makeStyles( }, bottom: 0, gridColumn: 2, - height: 70, position: "sticky", zIndex: 10 }, diff --git a/src/components/SaveButtonBar/SaveButtonBar.tsx b/src/components/SaveButtonBar/SaveButtonBar.tsx index 6086349f9..83ab6548a 100644 --- a/src/components/SaveButtonBar/SaveButtonBar.tsx +++ b/src/components/SaveButtonBar/SaveButtonBar.tsx @@ -41,7 +41,7 @@ const useStyles = makeStyles( background: theme.palette.background.default, borderTop: "1px solid transparent", boxShadow: `0 -5px 5px 0 ${theme.palette.divider}`, - height: "100%", + height: 70, transition: `box-shadow ${theme.transitions.duration.shortest}ms` }, spacer: { From 6e68c0b4da8f12ea90e61e0fbbf6d58aff896dd8 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 16 Jan 2020 15:37:46 +0100 Subject: [PATCH 71/85] Remove unused code --- src/components/Filter/FilterContent.tsx | 45 ------------------------- 1 file changed, 45 deletions(-) diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index 908329dfc..18a7d3fa6 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -436,51 +436,6 @@ const FilterContent: React.FC = ({ setDisplayValues={setAutocompleteDisplayValues} onFilterPropertyChange={onFilterPropertyChange} /> - // - // onFilterPropertyChange({ - // payload: { - // name: filterField.name, - // update: { - // value: toggle( - // event.target.value, - // filterField.value, - // (a, b) => a === b - // ) - // } - // }, - // type: "set-property" - // }), - // value => - // setAutocompleteDisplayValues({ - // ...autocompleteDisplayValues, - // [filterField.name]: toggle( - // value[0], - // autocompleteDisplayValues[filterField.name], - // (a, b) => a.value === b.value - // ) - // }), - // [], - // filterField.options - // )} - // fetchChoices={filterField.onSearchChange} - // loading={filterField.loading} - // data-tc={filterField.name} - // key={filterField.name} - // /> )}
)} From a30f52e2d8e1c3d9ed962fb9d43259e196578cc7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 16 Jan 2020 15:57:40 +0100 Subject: [PATCH 72/85] Improve options field --- src/components/Filter/Filter.tsx | 9 +++ .../Filter/FilterAutocompleteField.tsx | 7 +- src/components/Filter/FilterContent.tsx | 69 ++--------------- src/components/Filter/FilterOptionField.tsx | 74 +++++++++++++++++++ src/components/Filter/types.ts | 6 ++ 5 files changed, 99 insertions(+), 66 deletions(-) create mode 100644 src/components/Filter/FilterOptionField.tsx diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index f3c1f96d1..828141e1b 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -128,6 +128,15 @@ const Filter: React.FC = props => { transition disablePortal placement="bottom-start" + modifiers={{ + flip: { + enabled: false + }, + preventOverflow: { + boundariesElement: "scrollParent", + enabled: false + } + }} > {({ TransitionProps, placement }) => ( ; - filterField: IFilterElement; setDisplayValues: ( values: Record ) => void; - onFilterPropertyChange: React.Dispatch>; } const useStyles = makeStyles( diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index 18a7d3fa6..9a9021ccc 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -11,22 +11,18 @@ import makeStyles from "@material-ui/core/styles/makeStyles"; import { fade } from "@material-ui/core/styles/colorManipulator"; import { buttonMessages } from "@saleor/intl"; import { TextField } from "@material-ui/core"; -import { toggle } from "@saleor/utils/lists"; -import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import useStateFromProps from "@saleor/hooks/useStateFromProps"; import Hr from "../Hr"; import Checkbox from "../Checkbox"; import SingleSelectField from "../SingleSelectField"; import { SingleAutocompleteChoiceType } from "../SingleAutocompleteSelectField"; import FormSpacer from "../FormSpacer"; -import MultiAutocompleteSelectField, { - MultiAutocompleteChoiceType -} from "../MultiAutocompleteSelectField"; -import Link from "../Link"; +import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; import { IFilter, FieldType, FilterType } from "./types"; import Arrow from "./Arrow"; import { FilterReducerAction } from "./reducer"; import FilterAutocompleteField from "./FilterAutocompleteField"; +import FilterOptionField from "./FilterOptionField"; export interface FilterContentProps { currencySymbol: string; @@ -341,61 +337,12 @@ const FilterContent: React.FC = ({
)} - {filterField.type === FieldType.options && - (filterField.multiple ? ( - filterField.options.map(option => ( -
- - } - label={option.label} - name={filterField.name} - onChange={() => - onFilterPropertyChange({ - payload: { - name: filterField.name, - update: { - value: toggle( - option.value, - filterField.value, - (a, b) => a === b - ) - } - }, - type: "set-property" - }) - } - /> -
- )) - ) : ( - - onFilterPropertyChange({ - payload: { - name: filterField.name, - update: { - value: [event.target.value] - } - }, - type: "set-property" - }) - } - /> - ))} + {filterField.type === FieldType.options && ( + + )} {filterField.type === FieldType.boolean && filterField.options.map(option => (
({ + option: { + left: -theme.spacing(0.5), + position: "relative" + }, + optionRadio: { + left: -theme.spacing(0.25) + }, + root: {} + }), + { name: "FilterOptionField" } +); + +const FilterOptionField: React.FC = ({ + filterField, + onFilterPropertyChange +}) => { + const classes = useStyles({}); + const handleSelect = (value: string) => + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: filterField.multiple + ? toggle(value, filterField.value, (a, b) => a === b) + : [value] + } + }, + type: "set-property" + }); + + return ( +
+ {filterField.options.map(option => ( +
+ + ) : ( + + ) + } + label={option.label} + name={filterField.name} + onChange={() => handleSelect(option.value)} + /> +
+ ))} +
+ ); +}; + +FilterOptionField.displayName = "FilterOptionField"; +export default FilterOptionField; diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index ea257c8f7..86930048e 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -1,5 +1,6 @@ import { FetchMoreProps, SearchPageProps } from "@saleor/types"; import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; +import { FilterReducerAction } from "./reducer"; export enum FieldType { autocomplete, @@ -28,6 +29,11 @@ export interface IFilterElement type: FieldType; } +export interface FilterBaseFieldProps { + filterField: IFilterElement; + onFilterPropertyChange: React.Dispatch>; +} + export type IFilter = Array>; export enum FilterType { From 5dcc8c76e3b25439e97b1b708ab7d889168d63b5 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 16 Jan 2020 15:57:47 +0100 Subject: [PATCH 73/85] Fix types --- src/products/views/ProductList/filters.test.ts | 17 +++++++++++++++++ src/products/views/ProductList/filters.ts | 3 +-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts index 300a2ab15..a6addc42b 100644 --- a/src/products/views/ProductList/filters.test.ts +++ b/src/products/views/ProductList/filters.test.ts @@ -13,6 +13,7 @@ import { StockAvailability } from "@saleor/types/globalTypes"; import { categories } from "@saleor/categories/fixtures"; import { fetchMoreProps, searchPageProps } from "@saleor/fixtures"; import { collections } from "@saleor/collections/fixtures"; +import { productTypes } from "@saleor/productTypes/fixtures"; import { getFilterVariables, getFilterQueryParam } from "./filters"; describe("Filtering query params", () => { @@ -79,6 +80,22 @@ describe("Filtering URL params", () => { min: "10" } }, + productType: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + choices: productTypes.slice(3).map(category => ({ + label: category.name, + value: category.id + })), + displayValues: [ + { + label: productTypes[3].name, + value: productTypes[3].id + } + ], + value: [productTypes[4].id] + }, status: { active: false, value: ProductStatus.PUBLISHED diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 02d0f4725..539f18b4f 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -34,8 +34,7 @@ import { getMinMaxQueryParam, getSingleEnumValueQueryParam, dedupeFilter, - getMultipleValueQueryParam, - getSingleValueQueryParam + getMultipleValueQueryParam } from "../../../utils/filters"; import { ProductListUrlFilters, From 38bbcddd3d8eb1fb712c593dd2cc40a9435f63b3 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 16 Jan 2020 16:03:44 +0100 Subject: [PATCH 74/85] Add no results text --- src/components/Filter/FilterAutocompleteField.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/Filter/FilterAutocompleteField.tsx b/src/components/Filter/FilterAutocompleteField.tsx index 0d0b31aeb..7a909c899 100644 --- a/src/components/Filter/FilterAutocompleteField.tsx +++ b/src/components/Filter/FilterAutocompleteField.tsx @@ -1,5 +1,6 @@ import React from "react"; import FormControlLabel from "@material-ui/core/FormControlLabel"; +import Typography from "@material-ui/core/Typography"; import TextField from "@material-ui/core/TextField"; import makeStyles from "@material-ui/core/styles/makeStyles"; import { FormattedMessage } from "react-intl"; @@ -30,6 +31,9 @@ const useStyles = makeStyles( inputContainer: { marginBottom: theme.spacing(1) }, + noResults: { + marginTop: theme.spacing(1) + }, option: { left: -theme.spacing(0.5), position: "relative" @@ -56,9 +60,12 @@ const FilterAutocompleteField: React.FC = ({ displayValue => displayValue.value !== option.value ) ); + const displayNoResults = + availableOptions.length === 0 && fieldDisplayValues.length === 0; const displayHr = !( (fieldDisplayValues.length === 0 && availableOptions.length > 0) || - (availableOptions.length === 0 && fieldDisplayValues.length > 0) + (availableOptions.length === 0 && fieldDisplayValues.length > 0) || + displayNoResults ); const handleChange = (option: MultiAutocompleteChoiceType) => { @@ -110,6 +117,11 @@ const FilterAutocompleteField: React.FC = ({
))} {displayHr &&
} + {displayNoResults && ( + + + + )} {availableOptions.map(option => (
Date: Thu, 16 Jan 2020 16:11:50 +0100 Subject: [PATCH 75/85] Suppress popper.js warnings --- src/components/Filter/Filter.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index 828141e1b..6e5ca5d56 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -132,6 +132,9 @@ const Filter: React.FC = props => { flip: { enabled: false }, + hide: { + enabled: false + }, preventOverflow: { boundariesElement: "scrollParent", enabled: false From 8ddf66f1349f79fedef8be9275f0e312e76b098f Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 17 Jan 2020 15:25:50 +0100 Subject: [PATCH 76/85] Add attribute filter --- src/components/Filter/types.ts | 1 + .../components/ProductListPage/filters.ts | 22 +++++++- src/products/queries.ts | 14 +++++ .../types/InitialProductFilterData.ts | 26 +++++++++ src/products/urls.ts | 11 +++- .../views/ProductList/ProductList.tsx | 6 +-- .../views/ProductList/filters.test.ts | 11 ++++ src/products/views/ProductList/filters.ts | 53 +++++++++++++++++-- src/types.ts | 3 ++ src/utils/filters/filters.ts | 6 ++- src/utils/handlers/filterHandlers.ts | 8 +-- 11 files changed, 143 insertions(+), 18 deletions(-) diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index 86930048e..a7b2f28a4 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -24,6 +24,7 @@ export interface IFilterElement Partial { autocomplete?: boolean; displayValues?: MultiAutocompleteChoiceType[]; + group?: T; label: string; name: T; type: FieldType; diff --git a/src/products/components/ProductListPage/filters.ts b/src/products/components/ProductListPage/filters.ts index 1ac4bd5f7..32225b967 100644 --- a/src/products/components/ProductListPage/filters.ts +++ b/src/products/components/ProductListPage/filters.ts @@ -9,8 +9,10 @@ import { } from "@saleor/utils/filters/fields"; import { IFilter } from "@saleor/components/Filter"; import { sectionNames } from "@saleor/intl"; +import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; export enum ProductFilterKeys { + attributes = "attributes", categories = "categories", collections = "collections", status = "status", @@ -20,6 +22,13 @@ export enum ProductFilterKeys { } export interface ProductListFilterOpts { + attributes: Array< + FilterOpts & { + choices: MultiAutocompleteChoiceType[]; + name: string; + slug: string; + } + >; categories: FilterOpts & AutocompleteFilterOpts; collections: FilterOpts & AutocompleteFilterOpts; price: FilterOpts; @@ -167,6 +176,17 @@ export function createFilterStructure( } ), active: opts.productType.active - } + }, + ...opts.attributes.map(attr => ({ + ...createOptionsField( + attr.slug as any, + attr.name, + attr.value, + true, + attr.choices + ), + active: attr.active, + group: ProductFilterKeys.attributes + })) ]; } diff --git a/src/products/queries.ts b/src/products/queries.ts index ca5c1a273..99236c111 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -220,6 +220,20 @@ const initialProductFilterDataQuery = gql` $collections: [ID!] $productTypes: [ID!] ) { + attributes(first: 100, filter: { filterableInDashboard: true }) { + edges { + node { + id + name + slug + values { + id + name + slug + } + } + } + } categories(first: 100, filter: { ids: $categories }) { edges { node { diff --git a/src/products/types/InitialProductFilterData.ts b/src/products/types/InitialProductFilterData.ts index c4c323c0d..0c982e04b 100644 --- a/src/products/types/InitialProductFilterData.ts +++ b/src/products/types/InitialProductFilterData.ts @@ -6,6 +6,31 @@ // GraphQL query operation: InitialProductFilterData // ==================================================== +export interface InitialProductFilterData_attributes_edges_node_values { + __typename: "AttributeValue"; + id: string; + name: string | null; + slug: string | null; +} + +export interface InitialProductFilterData_attributes_edges_node { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + values: (InitialProductFilterData_attributes_edges_node_values | null)[] | null; +} + +export interface InitialProductFilterData_attributes_edges { + __typename: "AttributeCountableEdge"; + node: InitialProductFilterData_attributes_edges_node; +} + +export interface InitialProductFilterData_attributes { + __typename: "AttributeCountableConnection"; + edges: InitialProductFilterData_attributes_edges[]; +} + export interface InitialProductFilterData_categories_edges_node { __typename: "Category"; id: string; @@ -55,6 +80,7 @@ export interface InitialProductFilterData_productTypes { } export interface InitialProductFilterData { + attributes: InitialProductFilterData_attributes | null; categories: InitialProductFilterData_categories | null; collections: InitialProductFilterData_collections | null; productTypes: InitialProductFilterData_productTypes | null; diff --git a/src/products/urls.ts b/src/products/urls.ts index 0f56302ea..9441c04a5 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -10,7 +10,8 @@ import { Pagination, Sort, TabActionDialog, - FiltersWithMultipleValues + FiltersWithMultipleValues, + FiltersAsDictWithMultipleValues } from "../types"; const productSection = "/products/"; @@ -36,8 +37,14 @@ export enum ProductListUrlFiltersWithMultipleValues { collections = "collections", productTypes = "productTypes" } +export enum ProductListUrlFiltersAsDictWithMultipleValues { + attributes = "attributes" +} export type ProductListUrlFilters = Filters & - FiltersWithMultipleValues; + FiltersWithMultipleValues & + FiltersAsDictWithMultipleValues< + ProductListUrlFiltersAsDictWithMultipleValues + >; export enum ProductListUrlSortField { attribute = "attribute", name = "name", diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 5d735a38c..818a47395 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -82,11 +82,6 @@ export const ProductList: React.FC = ({ params }) => { ); const intl = useIntl(); const { data: initialFilterData } = useInitialProductFilterDataQuery({ - skip: !( - !!params.categories || - !!params.collections || - !!params.productTypes - ), variables: { categories: params.categories, collections: params.collections, @@ -196,6 +191,7 @@ export const ProductList: React.FC = ({ params }) => { const filterOpts = getFilterOpts( params, + maybe(() => initialFilterData.attributes.edges.map(edge => edge.node), []), { initial: maybe( () => initialFilterData.categories.edges.map(edge => edge.node), diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts index a6addc42b..6c5460dac 100644 --- a/src/products/views/ProductList/filters.test.ts +++ b/src/products/views/ProductList/filters.test.ts @@ -14,6 +14,7 @@ import { categories } from "@saleor/categories/fixtures"; import { fetchMoreProps, searchPageProps } from "@saleor/fixtures"; import { collections } from "@saleor/collections/fixtures"; import { productTypes } from "@saleor/productTypes/fixtures"; +import { attributes } from "@saleor/attributes/fixtures"; import { getFilterVariables, getFilterQueryParam } from "./filters"; describe("Filtering query params", () => { @@ -41,6 +42,16 @@ describe("Filtering URL params", () => { const intl = createIntl(config); const filters = createFilterStructure(intl, { + attributes: attributes.map(attr => ({ + active: false, + choices: attr.values.map(val => ({ + label: val.name, + value: val.slug + })), + name: attr.name, + slug: attr.slug, + value: [attr.values[0].slug, attr.values[2].slug] + })), categories: { ...fetchMoreProps, ...searchPageProps, diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 539f18b4f..69ba4463b 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -1,3 +1,5 @@ +import isArray from "lodash-es/isArray"; + import { maybe, findValueInEnum } from "@saleor/misc"; import { ProductFilterKeys, @@ -12,7 +14,8 @@ import { import { InitialProductFilterData_categories_edges_node, InitialProductFilterData_collections_edges_node, - InitialProductFilterData_productTypes_edges_node + InitialProductFilterData_productTypes_edges_node, + InitialProductFilterData_attributes_edges_node } from "@saleor/products/types/InitialProductFilterData"; import { SearchCollections, @@ -47,6 +50,7 @@ export const PRODUCT_FILTERS_KEY = "productFilters"; export function getFilterOpts( params: ProductListUrlFilters, + attributes: InitialProductFilterData_attributes_edges_node[], categories: { initial: InitialProductFilterData_categories_edges_node[]; search: UseSearchResult; @@ -61,6 +65,21 @@ export function getFilterOpts( } ): ProductListFilterOpts { return { + attributes: attributes + .sort((a, b) => (a.name > b.name ? 1 : -1)) + .map(attr => ({ + active: maybe(() => params.attributes[attr.slug].length > 0, false), + choices: attr.values.map(val => ({ + label: val.name, + value: val.slug + })), + name: attr.name, + slug: attr.slug, + value: + !!params.attributes && params.attributes[attr.slug] + ? params.attributes[attr.slug] + : [] + })), categories: { active: !!params.categories, choices: maybe( @@ -177,6 +196,15 @@ export function getFilterVariables( params: ProductListUrlFilters ): ProductFilterInput { return { + attributes: !!params.attributes + ? Object.keys(params.attributes).map(key => ({ + slug: key, + // It is possible for qs to parse values not as string[] but string + values: isArray(params.attributes[key]) + ? params.attributes[key] + : (([params.attributes[key]] as unknown) as string[]) + })) + : null, categories: params.categories !== undefined ? params.categories : null, collections: params.collections !== undefined ? params.collections : null, isPublished: @@ -198,9 +226,28 @@ export function getFilterVariables( } export function getFilterQueryParam( - filter: IFilterElement + filter: IFilterElement, + params: ProductListUrlFilters ): ProductListUrlFilters { - const { name } = filter; + const { active, group, name, value } = filter; + + if (!!group) { + if (active) { + return { + [group]: + params && params[group] + ? { + ...params[group], + [name]: [...params[group], value] + } + : { + [name]: [value] + } + }; + } + + return {}; + } switch (name) { case ProductFilterKeys.categories: diff --git a/src/types.ts b/src/types.ts index ec663ad21..9aea9641d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -135,6 +135,9 @@ export type Filters = Partial< export type FiltersWithMultipleValues = Partial< Record >; +export type FiltersAsDictWithMultipleValues = Partial< + Record> +>; export type Search = Partial<{ query: string; }>; diff --git a/src/utils/filters/filters.ts b/src/utils/filters/filters.ts index c80edc459..8ce699521 100644 --- a/src/utils/filters/filters.ts +++ b/src/utils/filters/filters.ts @@ -34,12 +34,16 @@ export function dedupeFilter(array: T[]): T[] { return Array.from(new Set(array)); } +export type GetFilterQueryParam< + TFilterKeys extends string, + TFilters extends object +> = (filter: IFilterElement, params?: object) => TFilters; export function getFilterQueryParams< TFilterKeys extends string, TUrlFilters extends object >( filter: IFilter, - getFilterQueryParam: (filter: IFilterElement) => TUrlFilters + getFilterQueryParam: GetFilterQueryParam ): TUrlFilters { return filter.reduce( (acc, filterField) => ({ diff --git a/src/utils/handlers/filterHandlers.ts b/src/utils/handlers/filterHandlers.ts index 3c70db069..60c4dbc3c 100644 --- a/src/utils/handlers/filterHandlers.ts +++ b/src/utils/handlers/filterHandlers.ts @@ -1,14 +1,10 @@ -import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { IFilter } from "@saleor/components/Filter"; import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; import { Sort, Pagination, ActiveTab, Search } from "@saleor/types"; -import { getFilterQueryParams } from "../filters"; +import { getFilterQueryParams, GetFilterQueryParam } from "../filters"; type RequiredParams = ActiveTab & Search & Sort & Pagination; type CreateUrl = (params: RequiredParams) => string; -type GetFilterQueryParam< - TFilterKeys extends string, - TFilters extends object -> = (filter: IFilterElement) => TFilters; type CreateFilterHandlers = [ (filter: IFilter) => void, () => void, From 5ca01db4eb5a6bfbf60b5ddfd0b06690b5b79905 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 17 Jan 2020 15:41:29 +0100 Subject: [PATCH 77/85] Fix types --- .../views/ProductList/filters.test.ts | 87 +------------------ src/products/views/ProductList/fixtures.ts | 86 ++++++++++++++++++ .../stories/products/ProductListPage.tsx | 23 +---- 3 files changed, 92 insertions(+), 104 deletions(-) create mode 100644 src/products/views/ProductList/fixtures.ts diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts index 6c5460dac..f5960b563 100644 --- a/src/products/views/ProductList/filters.test.ts +++ b/src/products/views/ProductList/filters.test.ts @@ -2,20 +2,13 @@ import { createIntl } from "react-intl"; import { stringify as stringifyQs } from "qs"; import { ProductListUrlFilters } from "@saleor/products/urls"; -import { - createFilterStructure, - ProductStatus -} from "@saleor/products/components/ProductListPage"; +import { createFilterStructure } from "@saleor/products/components/ProductListPage"; import { getFilterQueryParams } from "@saleor/utils/filters"; import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; import { config } from "@test/intl"; import { StockAvailability } from "@saleor/types/globalTypes"; -import { categories } from "@saleor/categories/fixtures"; -import { fetchMoreProps, searchPageProps } from "@saleor/fixtures"; -import { collections } from "@saleor/collections/fixtures"; -import { productTypes } from "@saleor/productTypes/fixtures"; -import { attributes } from "@saleor/attributes/fixtures"; import { getFilterVariables, getFilterQueryParam } from "./filters"; +import { productListFilterOpts } from "./fixtures"; describe("Filtering query params", () => { it("should be empty object if no params given", () => { @@ -41,81 +34,7 @@ describe("Filtering query params", () => { describe("Filtering URL params", () => { const intl = createIntl(config); - const filters = createFilterStructure(intl, { - attributes: attributes.map(attr => ({ - active: false, - choices: attr.values.map(val => ({ - label: val.name, - value: val.slug - })), - name: attr.name, - slug: attr.slug, - value: [attr.values[0].slug, attr.values[2].slug] - })), - categories: { - ...fetchMoreProps, - ...searchPageProps, - active: false, - choices: categories.slice(5).map(category => ({ - label: category.name, - value: category.id - })), - displayValues: [ - { - label: categories[5].name, - value: categories[5].id - } - ], - value: [categories[5].id] - }, - collections: { - ...fetchMoreProps, - ...searchPageProps, - active: false, - choices: collections.slice(5).map(category => ({ - label: category.name, - value: category.id - })), - displayValues: [ - { - label: collections[5].name, - value: collections[5].id - } - ], - value: [collections[5].id] - }, - price: { - active: false, - value: { - max: "20", - min: "10" - } - }, - productType: { - ...fetchMoreProps, - ...searchPageProps, - active: false, - choices: productTypes.slice(3).map(category => ({ - label: category.name, - value: category.id - })), - displayValues: [ - { - label: productTypes[3].name, - value: productTypes[3].id - } - ], - value: [productTypes[4].id] - }, - status: { - active: false, - value: ProductStatus.PUBLISHED - }, - stockStatus: { - active: false, - value: StockAvailability.IN_STOCK - } - }); + const filters = createFilterStructure(intl, productListFilterOpts); it("should be empty if no active filters", () => { const filterQueryParams = getFilterQueryParams( diff --git a/src/products/views/ProductList/fixtures.ts b/src/products/views/ProductList/fixtures.ts new file mode 100644 index 000000000..82c5812f5 --- /dev/null +++ b/src/products/views/ProductList/fixtures.ts @@ -0,0 +1,86 @@ +import { + ProductListFilterOpts, + ProductStatus +} from "@saleor/products/components/ProductListPage"; +import { attributes } from "@saleor/attributes/fixtures"; +import { fetchMoreProps, searchPageProps } from "@saleor/fixtures"; +import { categories } from "@saleor/categories/fixtures"; +import { collections } from "@saleor/collections/fixtures"; +import { productTypes } from "@saleor/productTypes/fixtures"; +import { StockAvailability } from "@saleor/types/globalTypes"; + +export const productListFilterOpts: ProductListFilterOpts = { + attributes: attributes.map(attr => ({ + active: false, + choices: attr.values.map(val => ({ + label: val.name, + value: val.slug + })), + name: attr.name, + slug: attr.slug, + value: [attr.values[0].slug, attr.values.length > 2 && attr.values[2].slug] + })), + categories: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + choices: categories.slice(5).map(category => ({ + label: category.name, + value: category.id + })), + displayValues: [ + { + label: categories[5].name, + value: categories[5].id + } + ], + value: [categories[5].id] + }, + collections: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + choices: collections.slice(5).map(category => ({ + label: category.name, + value: category.id + })), + displayValues: [ + { + label: collections[5].name, + value: collections[5].id + } + ], + value: [collections[5].id] + }, + price: { + active: false, + value: { + max: "20", + min: "10" + } + }, + productType: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + choices: productTypes.slice(3).map(category => ({ + label: category.name, + value: category.id + })), + displayValues: [ + { + label: productTypes[3].name, + value: productTypes[3].id + } + ], + value: [productTypes[4].id] + }, + status: { + active: false, + value: ProductStatus.PUBLISHED + }, + stockStatus: { + active: false, + value: StockAvailability.IN_STOCK + } +}; diff --git a/src/storybook/stories/products/ProductListPage.tsx b/src/storybook/stories/products/ProductListPage.tsx index 4e6cc84fb..483084f10 100644 --- a/src/storybook/stories/products/ProductListPage.tsx +++ b/src/storybook/stories/products/ProductListPage.tsx @@ -7,7 +7,7 @@ import { products as productListFixture } from "@saleor/products/fixtures"; import { ProductListUrlSortField } from "@saleor/products/urls"; import { attributes } from "@saleor/productTypes/fixtures"; import { ListViews } from "@saleor/types"; -import { StockAvailability } from "@saleor/types/globalTypes"; +import { productListFilterOpts } from "@saleor/products/views/ProductList/fixtures"; import { fetchMoreProps, filterPageProps, @@ -16,8 +16,7 @@ import { sortPageProps } from "../../../fixtures"; import ProductListPage, { - ProductListPageProps, - ProductStatus + ProductListPageProps } from "../../../products/components/ProductListPage"; import Decorator from "../../Decorator"; @@ -38,23 +37,7 @@ const props: ProductListPageProps = { activeAttributeSortId: undefined, availableInGridAttributes: attributes, defaultSettings: defaultListSettings[ListViews.PRODUCT_LIST], - filterOpts: { - price: { - active: false, - value: { - max: "30", - min: "10" - } - }, - status: { - active: false, - value: ProductStatus.PUBLISHED - }, - stockStatus: { - active: false, - value: StockAvailability.IN_STOCK - } - }, + filterOpts: productListFilterOpts, gridAttributes: attributes, products, settings: { From 5f3024d0b42791793f0a8c4695b4677191db7b4d Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 17 Jan 2020 15:52:50 +0100 Subject: [PATCH 78/85] Update snapshots --- .../__snapshots__/filters.test.ts.snap | 13 +- .../__snapshots__/Stories.test.ts.snap | 396 ++++++++++++++---- 2 files changed, 322 insertions(+), 87 deletions(-) diff --git a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap index 07f2f3bab..556446614 100644 --- a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap +++ b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap @@ -2,6 +2,14 @@ exports[`Filtering URL params should not be empty if active filters are present 1`] = ` Object { + "attributes": Object { + "size": Array [ + Array [ + "xs", + "m", + ], + ], + }, "categories": Array [ "878752", ], @@ -10,9 +18,12 @@ Object { ], "priceFrom": "10", "priceTo": "20", + "productTypes": Array [ + "UHJvZHVjdFR5cGU6MQ==", + ], "status": "published", "stockStatus": "IN_STOCK", } `; -exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B0%5D=878752&collections%5B0%5D=Q29sbGVjdGlvbjoc"`; +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B0%5D=878752&collections%5B0%5D=Q29sbGVjdGlvbjoc&productTypes%5B0%5D=UHJvZHVjdFR5cGU6MQ%3D%3D&attributes%5Bsize%5D%5B0%5D%5B0%5D=xs&attributes%5Bsize%5D%5B0%5D%5B1%5D=m"`; diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 2871c1ac9..d72da75a6 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -1671,55 +1671,167 @@ exports[`Storyshots Generics / Filter default 1`] = ` class="FilterContent-filterSettings-id" >
-
@@ -2017,55 +2129,167 @@ exports[`Storyshots Generics / Filter interactive 1`] = ` class="FilterContent-filterSettings-id" >
-
From 5fbde28cb44730d8bb455b88353339a16d58ce6e Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 20 Jan 2020 14:54:05 +0100 Subject: [PATCH 79/85] Fix select menus --- src/components/Filter/Filter.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index 6e5ca5d56..bcaeefc26 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -97,7 +97,13 @@ const Filter: React.FC = props => { const isFilterActive = menu.some(filterElement => filterElement.active); return ( - setFilterMenuOpened(false)}> + { + if ((event.target as HTMLElement).getAttribute("role") !== "option") { + setFilterMenuOpened(false); + } + }} + >
Date: Mon, 20 Jan 2020 16:12:20 +0100 Subject: [PATCH 80/85] Fix minor bugs --- src/products/index.tsx | 8 ++- .../__snapshots__/filters.test.ts.snap | 52 +++++++++++++++++-- src/products/views/ProductList/filters.ts | 27 ++++------ src/utils/filters/filters.ts | 2 +- src/utils/urls.ts | 13 +++++ 5 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/products/index.tsx b/src/products/index.tsx index fed57ca5a..2d04e98a0 100644 --- a/src/products/index.tsx +++ b/src/products/index.tsx @@ -5,6 +5,7 @@ import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; import { asSortParams } from "@saleor/utils/sort"; +import { getArrayQueryParam } from "@saleor/utils/urls"; import { WindowTitle } from "../components/WindowTitle"; import { productAddPath, @@ -29,7 +30,12 @@ import ProductVariantCreateComponent from "./views/ProductVariantCreate"; const ProductList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); const params: ProductListUrlQueryParams = asSortParams( - qs, + { + ...qs, + categories: getArrayQueryParam(qs.categories), + collections: getArrayQueryParam(qs.collections), + productTypes: getArrayQueryParam(qs.productTypes) + }, ProductListUrlSortField ); diff --git a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap index 556446614..7b7084a87 100644 --- a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap +++ b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap @@ -3,11 +3,53 @@ exports[`Filtering URL params should not be empty if active filters are present 1`] = ` Object { "attributes": Object { + "author": Array [ + "john-doe", + false, + ], + "box-size": Array [ + "100g", + "500g", + ], + "brand": Array [ + "saleor", + false, + ], + "candy-box-size": Array [ + "100g", + "500g", + ], + "coffee-genre": Array [ + "arabica", + false, + ], + "collar": Array [ + "round", + "polo", + ], + "color": Array [ + "blue", + false, + ], + "cover": Array [ + "soft", + "middle-soft", + ], + "flavor": Array [ + "sour", + false, + ], + "language": Array [ + "english", + false, + ], + "publisher": Array [ + "mirumee-press", + false, + ], "size": Array [ - Array [ - "xs", - "m", - ], + "xs", + "m", ], }, "categories": Array [ @@ -26,4 +68,4 @@ Object { } `; -exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B0%5D=878752&collections%5B0%5D=Q29sbGVjdGlvbjoc&productTypes%5B0%5D=UHJvZHVjdFR5cGU6MQ%3D%3D&attributes%5Bsize%5D%5B0%5D%5B0%5D=xs&attributes%5Bsize%5D%5B0%5D%5B1%5D=m"`; +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B0%5D=878752&collections%5B0%5D=Q29sbGVjdGlvbjoc&productTypes%5B0%5D=UHJvZHVjdFR5cGU6MQ%3D%3D&attributes%5Bauthor%5D%5B0%5D=john-doe&attributes%5Bauthor%5D%5B1%5D=false&attributes%5Bbox-size%5D%5B0%5D=100g&attributes%5Bbox-size%5D%5B1%5D=500g&attributes%5Bbrand%5D%5B0%5D=saleor&attributes%5Bbrand%5D%5B1%5D=false&attributes%5Bcandy-box-size%5D%5B0%5D=100g&attributes%5Bcandy-box-size%5D%5B1%5D=500g&attributes%5Bcoffee-genre%5D%5B0%5D=arabica&attributes%5Bcoffee-genre%5D%5B1%5D=false&attributes%5Bcollar%5D%5B0%5D=round&attributes%5Bcollar%5D%5B1%5D=polo&attributes%5Bcolor%5D%5B0%5D=blue&attributes%5Bcolor%5D%5B1%5D=false&attributes%5Bcover%5D%5B0%5D=soft&attributes%5Bcover%5D%5B1%5D=middle-soft&attributes%5Bflavor%5D%5B0%5D=sour&attributes%5Bflavor%5D%5B1%5D=false&attributes%5Blanguage%5D%5B0%5D=english&attributes%5Blanguage%5D%5B1%5D=false&attributes%5Bpublisher%5D%5B0%5D=mirumee-press&attributes%5Bpublisher%5D%5B1%5D=false&attributes%5Bsize%5D%5B0%5D=xs&attributes%5Bsize%5D%5B1%5D=m"`; diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 69ba4463b..2916e1414 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -215,8 +215,8 @@ export function getFilterVariables( gte: parseFloat(params.priceFrom), lte: parseFloat(params.priceTo) }), - productType: - params.productTypes !== undefined ? params.productTypes[0] : null, + productTypes: + params.productTypes !== undefined ? params.productTypes : null, search: params.query, stockAvailability: params.stockStatus !== undefined @@ -232,21 +232,16 @@ export function getFilterQueryParam( const { active, group, name, value } = filter; if (!!group) { - if (active) { - return { - [group]: - params && params[group] - ? { - ...params[group], - [name]: [...params[group], value] - } - : { - [name]: [value] - } - }; - } + const rest = params && params[group] ? params[group] : undefined; - return {}; + return { + [group]: active + ? { + ...(rest === undefined ? {} : rest), + [name]: value + } + : rest + }; } switch (name) { diff --git a/src/utils/filters/filters.ts b/src/utils/filters/filters.ts index 8ce699521..59979161b 100644 --- a/src/utils/filters/filters.ts +++ b/src/utils/filters/filters.ts @@ -48,7 +48,7 @@ export function getFilterQueryParams< return filter.reduce( (acc, filterField) => ({ ...acc, - ...getFilterQueryParam(filterField) + ...getFilterQueryParam(filterField, acc) }), {} as TUrlFilters ); diff --git a/src/utils/urls.ts b/src/utils/urls.ts index 332488a80..11791432d 100644 --- a/src/utils/urls.ts +++ b/src/utils/urls.ts @@ -1,7 +1,20 @@ import { stringify } from "qs"; +import isArray from "lodash-es/isArray"; export function stringifyQs(params: object): string { return stringify(params, { indices: false }); } + +export function getArrayQueryParam(param: string | string[]): string[] { + if (!param) { + return undefined; + } + + if (isArray(param)) { + return param; + } + + return [param]; +} From c17442ae95ddc603c704efbb169645acaadad2a0 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 20 Jan 2020 16:13:40 +0100 Subject: [PATCH 81/85] Fix homepage link --- src/home/views/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/home/views/index.tsx b/src/home/views/index.tsx index 8e6aaa1f8..d42da2298 100644 --- a/src/home/views/index.tsx +++ b/src/home/views/index.tsx @@ -48,7 +48,7 @@ const HomeSection = () => { onProductsOutOfStockClick={() => navigate( productListUrl({ - status: StockAvailability.OUT_OF_STOCK + stockStatus: StockAvailability.OUT_OF_STOCK }) ) } From 13db408919556b5ea975697d67b013a5a87627f5 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 20 Jan 2020 16:23:36 +0100 Subject: [PATCH 82/85] Fix collection filtering --- src/products/views/ProductList/filters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 2916e1414..6ec02cd39 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -129,7 +129,7 @@ export function getFilterOpts( })), [] ) - : undefined, + : [], hasMore: maybe( () => collections.search.result.data.search.pageInfo.hasNextPage, false From ca5a6536fee0116631fec6efc707945c588ddcf8 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 20 Jan 2020 16:49:51 +0100 Subject: [PATCH 83/85] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a49b61d..a2c745d0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ All notable, unreleased changes to this project will be documented in this file. - Stop using deprecated fields - #357 by @dominik-zeglen - Throw error when API_URI is not set - #375 by @dominik-zeglen - Fix variant stock input - #377 by @dominik-zeglen -- Refactor filtering components - #344 by @dominik-zeglen +- Add filtering to views - #361 by @dominik-zeglen ## 2.0.0 From 6afda334befcf0fdac5d7a587f9ffe94ca907aed Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 20 Jan 2020 17:06:05 +0100 Subject: [PATCH 84/85] Update snapshots --- .../__snapshots__/Stories.test.ts.snap | 210 +++++++++--------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index d72da75a6..2d8f82911 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -86562,7 +86562,7 @@ exports[`Storyshots Views / Products / Create multiple variants / summary defaul > ​ @@ -86680,7 +86680,7 @@ exports[`Storyshots Views / Products / Create multiple variants / summary defaul > ​ @@ -97552,99 +97552,6 @@ Ctrl + K"
-
-
-
-
-
-
- Id sit dolores adipisci -
-
-
-
-
- Id sit dolores adipisci -
-
-
-
-
- Id sit dolores adipisci -
-
-
-
-
- Id sit dolores adipisci -
-
-
-
-
- Id sit dolores adipisci -
-
-
+
+
+
+
+
+
+ Id sit dolores adipisci +
+
+
+
+
+ Id sit dolores adipisci +
+
+
+
+
+ Id sit dolores adipisci +
+
+
+
+
+ Id sit dolores adipisci +
+
+
+
+
+ Id sit dolores adipisci +
+
+
​ @@ -99359,7 +99359,7 @@ Ctrl + K" > ​ @@ -104730,7 +104730,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = ` class="MuiTableCell-root-id MuiTableCell-body-id ProductList-colType-id" data-tc="product-type" > - Top (clothing) + Juice - Juice + Top (clothing) - Black Hoodie + Polo Shirt
@@ -105112,7 +105112,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = `
- Blue Hoodie + Black Hoodie
@@ -105177,7 +105177,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = `
- Mustard Hoodie + Blue Hoodie
@@ -105242,7 +105242,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = `
- Polo Shirt + Mustard Hoodie
@@ -105250,7 +105250,7 @@ exports[`Storyshots Views / Products / Product list default 1`] = ` class="MuiTableCell-root-id MuiTableCell-body-id ProductList-colType-id" data-tc="product-type" > - Cushion + Top (clothing) - Top (clothing) + Cushion Date: Mon, 20 Jan 2020 17:07:26 +0100 Subject: [PATCH 85/85] Update messages --- locale/messages.pot | 478 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 405 insertions(+), 73 deletions(-) diff --git a/locale/messages.pot b/locale/messages.pot index 27e30eb13..16ddf2c07 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-12-30T13:44:02.183Z\n" +"POT-Creation-Date: 2020-01-20T16:06:58.433Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -95,6 +95,22 @@ msgctxt "table actions" msgid "Actions" msgstr "" +#: build/locale/src/discounts/components/SaleListPage/filters.json +#. [src.discounts.components.SaleListPage.active] - sale status +#. defaultMessage is: +#. Active +msgctxt "sale status" +msgid "Active" +msgstr "" + +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.active] - voucher status +#. defaultMessage is: +#. Active +msgctxt "voucher status" +msgid "Active" +msgstr "" + #: build/locale/src/plugins/components/PluginsList/PluginsList.json #. [src.plugins.components.PluginsList.3247064221] - plugin status #. defaultMessage is: @@ -103,6 +119,22 @@ msgctxt "plugin status" msgid "Active" msgstr "" +#: build/locale/src/plugins/components/PluginsListPage/filters.json +#. [src.plugins.components.PluginsListPage.active] - plugin +#. defaultMessage is: +#. Active +msgctxt "plugin" +msgid "Active" +msgstr "" + +#: build/locale/src/services/components/ServiceListPage/filters.json +#. [src.services.components.ServiceListPage.active] - service account +#. defaultMessage is: +#. Active +msgctxt "service account" +msgid "Active" +msgstr "" + #: build/locale/src/staff/components/StaffList/StaffList.json #. [src.staff.components.StaffList.3247064221] - staff member status #. defaultMessage is: @@ -111,6 +143,22 @@ msgctxt "staff member status" msgid "Active" msgstr "" +#: build/locale/src/staff/components/StaffListPage/filters.json +#. [src.staff.components.StaffListPage.active] - staff member's account +#. defaultMessage is: +#. Active +msgctxt "staff member's account" +msgid "Active" +msgstr "" + +#: build/locale/src/webhooks/components/WebhooksListPage/filters.json +#. [src.webhooks.components.WebhooksListPage.active] - webhook +#. defaultMessage is: +#. Active +msgctxt "webhook" +msgid "Active" +msgstr "" + #: build/locale/src/discounts/components/DiscountDates/DiscountDates.json #. [src.discounts.components.DiscountDates.1662220323] - time during discount is active, header #. defaultMessage is: @@ -535,6 +583,14 @@ msgctxt "section header" msgid "All Photos" msgstr "" +#: build/locale/src/plugins/components/PluginsListPage/PluginsListPage.json +#. [src.plugins.components.PluginsListPage.3523826683] - tab name +#. defaultMessage is: +#. All Plugins +msgctxt "tab name" +msgid "All Plugins" +msgstr "" + #: build/locale/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.json #. [src.productTypes.components.ProductTypeListPage.1776073799] - tab name #. defaultMessage is: @@ -1511,6 +1567,14 @@ msgctxt "description" msgid "Availability" msgstr "" +#: build/locale/src/products/components/ProductListPage/filters.json +#. [src.products.components.ProductListPage.available] - product status +#. defaultMessage is: +#. Available +msgctxt "product status" +msgid "Available" +msgstr "" + #: build/locale/src/products/components/ProductVariants/ProductVariants.json #. [src.products.components.ProductVariants.2157131639] - product variant status #. defaultMessage is: @@ -1519,14 +1583,6 @@ msgctxt "product variant status" msgid "Available" msgstr "" -#: build/locale/src/products/views/ProductList/messages.json -#. [src.products.views.ProductList.available] - product status -#. defaultMessage is: -#. Available -msgctxt "product status" -msgid "Available" -msgstr "" - #: build/locale/src/taxes/components/CountryTaxesPage/CountryTaxesPage.json #. [src.taxes.components.CountryTaxesPage.2201910191] - tax rate #. defaultMessage is: @@ -1603,6 +1659,14 @@ msgctxt "tax rate" msgid "Books" msgstr "" +#: build/locale/src/attributes/components/AttributeListPage/filters.json +#. [src.attributes.components.AttributeListPage.availableInGrid] - attribute can be column in product list table +#. defaultMessage is: +#. Can be used as column +msgctxt "attribute can be column in product list table" +msgid "Can be used as column" +msgstr "" + #: build/locale/src/intl.json #. [src.cancel] - button #. defaultMessage is: @@ -1851,6 +1915,14 @@ msgctxt "button" msgid "Change your password" msgstr "" +#: build/locale/src/webhooks/components/WebhookEvents/WebhookEvents.json +#. [src.webhooks.components.WebhookEvents.40035964] - event +#. defaultMessage is: +#. Changed quantity in checkout +msgctxt "event" +msgid "Changed quantity in checkout" +msgstr "" + #: build/locale/src/products/components/ProductPricing/ProductPricing.json #. [src.products.components.ProductPricing.3015886868] #. defaultMessage is: @@ -2051,6 +2123,10 @@ msgstr "" #. [src.productTypes.components.ProductTypeList.2754779425] - product type #. defaultMessage is: #. Configurable +#: build/locale/src/productTypes/components/ProductTypeListPage/filters.json +#. [src.productTypes.components.ProductTypeListPage.configurable] - product type +#. defaultMessage is: +#. Configurable msgctxt "product type" msgid "Configurable" msgstr "" @@ -2719,6 +2795,14 @@ msgctxt "button" msgid "Create webhook" msgstr "" +#: build/locale/src/orders/components/OrderDraftListPage/filters.json +#. [src.orders.components.OrderDraftListPage.created] - draft order +#. defaultMessage is: +#. Created +msgctxt "draft order" +msgid "Created" +msgstr "" + #: build/locale/src/collections/views/CollectionCreate.json #. [src.collections.views.1597339737] #. defaultMessage is: @@ -2783,6 +2867,14 @@ msgctxt "description" msgid "Customer" msgstr "" +#: build/locale/src/orders/components/OrderDraftListPage/filters.json +#. [src.orders.components.OrderDraftListPage.customer] - draft order +#. defaultMessage is: +#. Customer +msgctxt "draft order" +msgid "Customer" +msgstr "" + #: build/locale/src/orders/components/OrderList/OrderList.json #. [src.orders.components.OrderList.3426593715] - e-mail or full name #. defaultMessage is: @@ -2903,6 +2995,14 @@ msgctxt "date when order was placed" msgid "Date" msgstr "" +#: build/locale/src/staff/components/StaffListPage/filters.json +#. [src.staff.components.StaffListPage.deactivated] - staff member's account +#. defaultMessage is: +#. Deactivated +msgctxt "staff member's account" +msgid "Deactivated" +msgstr "" + #: build/locale/src/customers/components/CustomerAddress/CustomerAddress.json #. [src.customers.components.CustomerAddress.1224809208] #. defaultMessage is: @@ -3399,6 +3499,14 @@ msgctxt "product type" msgid "Digital" msgstr "" +#: build/locale/src/productTypes/components/ProductTypeListPage/filters.json +#. [src.productTypes.components.ProductTypeListPage.digital] - product +#. defaultMessage is: +#. Digital +msgctxt "product" +msgid "Digital" +msgstr "" + #: build/locale/src/translations/components/TranslationFields/TranslationFieldsSave.json #. [src.translations.components.TranslationFields.363646127] - button #. defaultMessage is: @@ -3423,6 +3531,18 @@ msgctxt "description" msgid "Discount Code" msgstr "" +#: build/locale/src/discounts/components/SaleListPage/filters.json +#. [src.discounts.components.SaleListPage.type] +#. defaultMessage is: +#. Discount Type +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.type] +#. defaultMessage is: +#. Discount Type +msgctxt "description" +msgid "Discount Type" +msgstr "" + #: build/locale/src/discounts/components/SaleType/SaleType.json #. [src.discounts.components.SaleType.3216816841] - percentage or fixed, header #. defaultMessage is: @@ -3815,6 +3935,22 @@ msgctxt "webhook events" msgid "Expand or restrict webhooks permissions to register certain events in Saleor system." msgstr "" +#: build/locale/src/discounts/components/SaleListPage/filters.json +#. [src.discounts.components.SaleListPage.expired] - sale status +#. defaultMessage is: +#. Expired +msgctxt "sale status" +msgid "Expired" +msgstr "" + +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.expired] - voucher status +#. defaultMessage is: +#. Expired +msgctxt "voucher status" +msgid "Expired" +msgstr "" + #: build/locale/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.json #. [src.collections.components.CollectionDetailsPage.2906897537] - switch button #. defaultMessage is: @@ -3839,6 +3975,22 @@ msgctxt "search box label" msgid "Filter Countries" msgstr "" +#: build/locale/src/attributes/components/AttributeListPage/filters.json +#. [src.attributes.components.AttributeListPage.filterableInDashboard] - use attribute in filtering +#. defaultMessage is: +#. Filterable in Dashboard +msgctxt "use attribute in filtering" +msgid "Filterable in Dashboard" +msgstr "" + +#: build/locale/src/attributes/components/AttributeListPage/filters.json +#. [src.attributes.components.AttributeListPage.filterableInStorefront] - use attribute in filtering +#. defaultMessage is: +#. Filterable in Storefront +msgctxt "use attribute in filtering" +msgid "Filterable in Storefront" +msgstr "" + #: build/locale/src/components/Filter/Filter.json #. [src.components.Filter.996289613] - button #. defaultMessage is: @@ -3911,6 +4063,18 @@ msgctxt "voucher discount type" msgid "Fixed Amount" msgstr "" +#: build/locale/src/discounts/components/SaleListPage/filters.json +#. [src.discounts.components.SaleListPage.fixed] - discount type +#. defaultMessage is: +#. Fixed amount +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.fixed] - discount type +#. defaultMessage is: +#. Fixed amount +msgctxt "discount type" +msgid "Fixed amount" +msgstr "" + #: build/locale/src/taxes/components/CountryTaxesPage/CountryTaxesPage.json #. [src.taxes.components.CountryTaxesPage.2332795012] - tax rate #. defaultMessage is: @@ -3955,10 +4119,6 @@ msgstr "" #. [src.fulfilled] - order status #. defaultMessage is: #. Fulfilled -#: build/locale/src/orders/views/OrderList/messages.json -#. [src.orders.views.OrderList.fulfilled] - order status -#. defaultMessage is: -#. Fulfilled msgctxt "order status" msgid "Fulfilled" msgstr "" @@ -4099,6 +4259,14 @@ msgctxt "subheader" msgid "Here is some information we gathered about your store" msgstr "" +#: build/locale/src/collections/components/CollectionListPage/filters.json +#. [src.collections.components.CollectionListPage.hidden] - collection +#. defaultMessage is: +#. Hidden +msgctxt "collection" +msgid "Hidden" +msgstr "" + #: build/locale/src/components/VisibilityCard/VisibilityCard.json #. [src.components.VisibilityCard.77815154] #. defaultMessage is: @@ -4107,8 +4275,8 @@ msgctxt "description" msgid "Hidden" msgstr "" -#: build/locale/src/products/views/ProductList/messages.json -#. [src.products.views.ProductList.hidden] - product is hidden +#: build/locale/src/products/components/ProductListPage/filters.json +#. [src.products.components.ProductListPage.hidden] - product is hidden #. defaultMessage is: #. Hidden msgctxt "product is hidden" @@ -4223,6 +4391,22 @@ msgctxt "section header" msgid "Images" msgstr "" +#: build/locale/src/plugins/components/PluginsListPage/filters.json +#. [src.plugins.components.PluginsListPage.deactivated] - plugin +#. defaultMessage is: +#. Inactive +msgctxt "plugin" +msgid "Inactive" +msgstr "" + +#: build/locale/src/services/components/ServiceListPage/filters.json +#. [src.services.components.ServiceListPage.deactivated] - service account +#. defaultMessage is: +#. Inactive +msgctxt "service account" +msgid "Inactive" +msgstr "" + #: build/locale/src/staff/components/StaffList/StaffList.json #. [src.staff.components.StaffList.1004218338] - staff member status #. defaultMessage is: @@ -4231,6 +4415,14 @@ msgctxt "staff member status" msgid "Inactive" msgstr "" +#: build/locale/src/webhooks/components/WebhooksListPage/filters.json +#. [src.webhooks.components.WebhooksListPage.inactive] - webhook +#. defaultMessage is: +#. Inactive +msgctxt "webhook" +msgid "Inactive" +msgstr "" + #: build/locale/src/products/components/ProductStock/ProductStock.json #. [productStockHeader] - product stock, section header #. defaultMessage is: @@ -4303,6 +4495,14 @@ msgctxt "description" msgid "Items successfully fulfilled" msgstr "" +#: build/locale/src/customers/components/CustomerListPage/filters.json +#. [src.customers.components.CustomerListPage.joinDate] - customer +#. defaultMessage is: +#. Join Date +msgctxt "customer" +msgid "Join Date" +msgstr "" + #: build/locale/src/services/components/ServiceTokens/ServiceTokens.json #. [src.services.components.ServiceTokens.2446088470] - service account key #. defaultMessage is: @@ -4659,6 +4859,14 @@ msgctxt "description" msgid "Miscellaneous" msgstr "" +#: build/locale/src/customers/components/CustomerListPage/filters.json +#. [src.customers.components.CustomerListPage.moneySpent] - customer +#. defaultMessage is: +#. Money Spent +msgctxt "customer" +msgid "Money Spent" +msgstr "" + #: build/locale/src/attributes/components/AttributeDetails/AttributeDetails.json #. [src.attributes.components.AttributeDetails.3334509011] - product attribute type #. defaultMessage is: @@ -5147,6 +5355,14 @@ msgctxt "description" msgid "No results" msgstr "" +#: build/locale/src/components/Filter/FilterAutocompleteField.json +#. [src.components.Filter.2332404293] - search +#. defaultMessage is: +#. No results +msgctxt "search" +msgid "No results" +msgstr "" + #: build/locale/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectFieldContent.json #. [src.components.MultiAutocompleteSelectField.4205644805] #. defaultMessage is: @@ -5475,6 +5691,14 @@ msgctxt "notes about customer, header" msgid "Notes" msgstr "" +#: build/locale/src/customers/components/CustomerListPage/filters.json +#. [src.customers.components.CustomerListPage.numberOfOrders] +#. defaultMessage is: +#. Number of Orders +msgctxt "description" +msgid "Number of Orders" +msgstr "" + #: build/locale/src/discounts/components/VoucherValue/VoucherValue.json #. [src.discounts.components.VoucherValue.1492866942] - voucher application, switch button #. defaultMessage is: @@ -5567,14 +5791,6 @@ msgctxt "navigator placeholder" msgid "Order Number" msgstr "" -#: build/locale/src/orders/views/OrderList/messages.json -#. [src.orders.views.OrderList.status] -#. defaultMessage is: -#. Order Status -msgctxt "description" -msgid "Order Status" -msgstr "" - #: build/locale/src/orders/components/OrderHistory/OrderHistory.json #. [src.orders.components.OrderHistory.1230178536] - order history message #. defaultMessage is: @@ -5779,8 +5995,8 @@ msgctxt "navigator notification" msgid "Our new feature to help you with your daily tasks. Run Navigator using {keyboardShortcut} shortcut." msgstr "" -#: build/locale/src/products/views/ProductList/messages.json -#. [src.products.views.ProductList.outOfStock] - product status +#: build/locale/src/products/components/ProductListPage/filters.json +#. [src.products.components.ProductListPage.outOfStock] - product status #. defaultMessage is: #. Out Of Stock msgctxt "product status" @@ -5827,14 +6043,6 @@ msgctxt "description" msgid "Pages" msgstr "" -#: build/locale/src/orders/views/OrderList/messages.json -#. [src.orders.views.OrderList.partiallyFulfilled] - order status -#. defaultMessage is: -#. Partially Fulfilled -msgctxt "order status" -msgid "Partially Fulfilled" -msgstr "" - #: build/locale/src/misc.json #. [src.partiallyFulfilled] - order status #. defaultMessage is: @@ -5975,10 +6183,18 @@ msgctxt "order history message" msgid "Payment was voided" msgstr "" +#: build/locale/src/discounts/components/SaleListPage/filters.json +#. [src.discounts.components.SaleListPage.percentage] - discount type +#. defaultMessage is: +#. Percentage #: build/locale/src/discounts/components/SaleType/SaleType.json #. [src.discounts.components.SaleType.3688224049] - discount type #. defaultMessage is: #. Percentage +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.percentage] - discount type +#. defaultMessage is: +#. Percentage msgctxt "discount type" msgid "Percentage" msgstr "" @@ -6059,8 +6275,8 @@ msgctxt "product type" msgid "Physical" msgstr "" -#: build/locale/src/orders/views/OrderList/messages.json -#. [src.orders.views.OrderList.placed] - order +#: build/locale/src/orders/components/OrderListPage/filters.json +#. [src.orders.components.OrderListPage.placed] - order #. defaultMessage is: #. Placed msgctxt "order" @@ -6211,6 +6427,18 @@ msgctxt "product unit price" msgid "Price" msgstr "" +#: build/locale/src/products/components/ProductListPage/filters.json +#. [src.products.components.ProductListPage.price] +#. defaultMessage is: +#. Price +#: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json +#. [productVariantCreatePricesPriceInputLabel] +#. defaultMessage is: +#. Price +msgctxt "description" +msgid "Price" +msgstr "" + #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json #. [src.products.components.ProductVariantCreateDialog.1134347598] - variant price, header #. defaultMessage is: @@ -6219,18 +6447,6 @@ msgctxt "variant price, header" msgid "Price" msgstr "" -#: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json -#. [productVariantCreatePricesPriceInputLabel] -#. defaultMessage is: -#. Price -#: build/locale/src/products/views/ProductList/messages.json -#. [src.products.views.ProductList.price] -#. defaultMessage is: -#. Price -msgctxt "description" -msgid "Price" -msgstr "" - #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json #. [productVariantCreatePricesSetPricePlaceholder] - variant price #. defaultMessage is: @@ -6639,6 +6855,18 @@ msgctxt "collection is published" msgid "Published" msgstr "" +#: build/locale/src/collections/components/CollectionListPage/filters.json +#. [src.collections.components.CollectionListPage.published] - collection +#. defaultMessage is: +#. Published +#: build/locale/src/components/Navigator/modes/messages.json +#. [src.components.Navigator.modes.collectionPublished] - collection +#. defaultMessage is: +#. Published +msgctxt "collection" +msgid "Published" +msgstr "" + #: build/locale/src/collections/components/CollectionProducts/CollectionProducts.json #. [src.collections.components.CollectionProducts.3640454975] - product is published #. defaultMessage is: @@ -6651,14 +6879,6 @@ msgctxt "product is published" msgid "Published" msgstr "" -#: build/locale/src/components/Navigator/modes/messages.json -#. [src.components.Navigator.modes.collectionPublished] - collection -#. defaultMessage is: -#. Published -msgctxt "collection" -msgid "Published" -msgstr "" - #: build/locale/src/pages/components/PageList/PageList.json #. [src.pages.components.PageList.3640454975] - page status #. defaultMessage is: @@ -6747,14 +6967,6 @@ msgctxt "shipping method price" msgid "Rate Price" msgstr "" -#: build/locale/src/orders/views/OrderList/messages.json -#. [src.orders.views.OrderList.readyToCapture] - order status -#. defaultMessage is: -#. Ready to Capture -msgctxt "order status" -msgid "Ready to Capture" -msgstr "" - #: build/locale/src/customers/components/CustomerOrders/CustomerOrders.json #. [src.customers.components.CustomerOrders.1899831623] - section header #. defaultMessage is: @@ -7035,6 +7247,22 @@ msgctxt "description" msgid "Saved changes" msgstr "" +#: build/locale/src/discounts/components/SaleListPage/filters.json +#. [src.discounts.components.SaleListPage.scheduled] - sale status +#. defaultMessage is: +#. Scheduled +msgctxt "sale status" +msgid "Scheduled" +msgstr "" + +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.scheduled] - voucher status +#. defaultMessage is: +#. Scheduled +msgctxt "voucher status" +msgid "Scheduled" +msgstr "" + #: build/locale/src/attributes/components/AttributeListPage/AttributeListPage.json #. [src.attributes.components.AttributeListPage.3916653510] #. defaultMessage is: @@ -7239,6 +7467,14 @@ msgctxt "description" msgid "Search Page" msgstr "" +#: build/locale/src/plugins/components/PluginsListPage/PluginsListPage.json +#. [src.plugins.components.PluginsListPage.3233248823] +#. defaultMessage is: +#. Search Plugins... +msgctxt "description" +msgid "Search Plugins..." +msgstr "" + #: build/locale/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.json #. [src.translations.components.TranslationsEntitiesListPage.2105464697] #. defaultMessage is: @@ -7615,6 +7851,14 @@ msgctxt "voucher discount" msgid "Shipment" msgstr "" +#: build/locale/src/productTypes/components/ProductTypeListPage/filters.json +#. [src.productTypes.components.ProductTypeListPage.shippable] - product +#. defaultMessage is: +#. Shippable +msgctxt "product" +msgid "Shippable" +msgstr "" + #: build/locale/src/orders/components/OrderPayment/OrderPayment.json #. [src.orders.components.OrderPayment.1325966144] - order shipping method name #. defaultMessage is: @@ -7743,6 +7987,14 @@ msgctxt "description" msgid "Show gross prices to customers in the storefront" msgstr "" +#: build/locale/src/components/Filter/FilterAutocompleteField.json +#. [src.components.Filter.4190318230] - search results +#. defaultMessage is: +#. Show more +msgctxt "search results" +msgid "Show more" +msgstr "" + #: build/locale/src/products/components/ProductOrganization/ProductOrganization.json #. [src.products.components.ProductOrganization.150865454] - product is not configurable #. defaultMessage is: @@ -7891,6 +8143,22 @@ msgctxt "description" msgid "Start typing to begin search..." msgstr "" +#: build/locale/src/discounts/components/SaleListPage/filters.json +#. [src.discounts.components.SaleListPage.started] - sale start date +#. defaultMessage is: +#. Started +msgctxt "sale start date" +msgid "Started" +msgstr "" + +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.started] - voucher start date +#. defaultMessage is: +#. Started +msgctxt "voucher start date" +msgid "Started" +msgstr "" + #: build/locale/src/discounts/components/SaleList/SaleList.json #. [src.discounts.components.SaleList.47059407] - sale start date #. defaultMessage is: @@ -7915,6 +8183,30 @@ msgctxt "order status" msgid "Status" msgstr "" +#: build/locale/src/discounts/components/SaleListPage/filters.json +#. [src.discounts.components.SaleListPage.status] - sale status +#. defaultMessage is: +#. Status +msgctxt "sale status" +msgid "Status" +msgstr "" + +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.status] - voucher status +#. defaultMessage is: +#. Status +msgctxt "voucher status" +msgid "Status" +msgstr "" + +#: build/locale/src/intl.json +#. [src.status] +#. defaultMessage is: +#. Status +msgctxt "description" +msgid "Status" +msgstr "" + #: build/locale/src/plugins/components/PluginInfo/PluginInfo.json #. [src.plugins.components.PluginInfo.1756106276] - plugin status #. defaultMessage is: @@ -7931,6 +8223,14 @@ msgctxt "product variant status" msgid "Status" msgstr "" +#: build/locale/src/staff/components/StaffListPage/filters.json +#. [src.staff.components.StaffListPage.status] - staff member's account +#. defaultMessage is: +#. Status +msgctxt "staff member's account" +msgid "Status" +msgstr "" + #: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.json #. [src.products.components.ProductVariantCreateDialog.3841616483] - variant stock, header #. defaultMessage is: @@ -7963,8 +8263,8 @@ msgctxt "product variant stock, section header" msgid "Stock" msgstr "" -#: build/locale/src/products/views/ProductList/messages.json -#. [src.products.views.ProductList.quantity] - product +#: build/locale/src/products/components/ProductListPage/filters.json +#. [src.products.components.ProductListPage.quantity] - product #. defaultMessage is: #. Stock quantity msgctxt "product" @@ -8399,6 +8699,14 @@ msgctxt "email sender" msgid "This will be visible as \"from\" name" msgstr "" +#: build/locale/src/discounts/components/VoucherListPage/filters.json +#. [src.discounts.components.VoucherListPage.timesUsed] - voucher +#. defaultMessage is: +#. Times used +msgctxt "voucher" +msgid "Times used" +msgstr "" + #: build/locale/src/pages/components/PageInfo/PageInfo.json #. [src.pages.components.PageInfo.1124600214] - page title #. defaultMessage is: @@ -8651,6 +8959,14 @@ msgctxt "product type is either simple or configurable" msgid "Type" msgstr "" +#: build/locale/src/productTypes/components/ProductTypeListPage/filters.json +#. [src.productTypes.components.ProductTypeListPage.type] - product type is digital or physical +#. defaultMessage is: +#. Type +msgctxt "product type is digital or physical" +msgid "Type" +msgstr "" + #: build/locale/src/components/Navigator/NavigatorInput.json #. [src.components.Navigator.1167695965] - navigator placeholder #. defaultMessage is: @@ -8863,10 +9179,6 @@ msgstr "" #. [src.unfulfilled] - order status #. defaultMessage is: #. Unfulfilled -#: build/locale/src/orders/views/OrderList/messages.json -#. [src.orders.views.OrderList.unfulfilled] - order status -#. defaultMessage is: -#. Unfulfilled msgctxt "order status" msgid "Unfulfilled" msgstr "" @@ -9135,6 +9447,14 @@ msgctxt "check to require attribute to have value" msgid "Value Required" msgstr "" +#: build/locale/src/attributes/components/AttributeListPage/filters.json +#. [src.attributes.components.AttributeListPage.valueRequired] - attribute value is required +#. defaultMessage is: +#. Value Required +msgctxt "attribute value is required" +msgid "Value Required" +msgstr "" + #: build/locale/src/attributes/views/AttributeDetails/AttributeDetails.json #. [src.attributes.views.AttributeDetails.423042761] - attribute value deleted #. defaultMessage is: @@ -9191,6 +9511,14 @@ msgctxt "section header" msgid "Variant Attributes" msgstr "" +#: build/locale/src/attributes/components/AttributeListPage/filters.json +#. [src.attributes.components.AttributeListPage.isVariantOnly] - attribute can be used only in variants +#. defaultMessage is: +#. Variant Only +msgctxt "attribute can be used only in variants" +msgid "Variant Only" +msgstr "" + #: build/locale/src/products/views/ProductVariant.json #. [src.products.views.2279302139] #. defaultMessage is: @@ -9267,8 +9595,8 @@ msgctxt "page status" msgid "Visibility" msgstr "" -#: build/locale/src/products/views/ProductList/messages.json -#. [src.products.views.ProductList.visibility] - product visibility +#: build/locale/src/products/components/ProductListPage/filters.json +#. [src.products.components.ProductListPage.visibility] - product visibility #. defaultMessage is: #. Visibility msgctxt "product visibility" @@ -9291,14 +9619,18 @@ msgctxt "description" msgid "Visible" msgstr "" -#: build/locale/src/products/views/ProductList/messages.json -#. [src.products.views.ProductList.visible] - product is visible +#: build/locale/src/products/components/ProductListPage/filters.json +#. [src.products.components.ProductListPage.visible] - product is visible #. defaultMessage is: #. Visible msgctxt "product is visible" msgid "Visible" msgstr "" +#: build/locale/src/attributes/components/AttributeListPage/filters.json +#. [src.attributes.components.AttributeListPage.visibleInStorefront] - attribute +#. defaultMessage is: +#. Visible on Product Page in Storefront #: build/locale/src/attributes/components/AttributeProperties/AttributeProperties.json #. [src.attributes.components.AttributeProperties.3876764312] - attribute #. defaultMessage is: