Merge pull request #212 from mirumee/fix/autocomplete-ux
Improve autocomplete ux
This commit is contained in:
commit
3936e68a36
67 changed files with 3226 additions and 1455 deletions
|
@ -37,3 +37,4 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
- Do not send customer invitation email - #211 by @dominik-zeglen
|
- Do not send customer invitation email - #211 by @dominik-zeglen
|
||||||
- Send address update mutation only once - #210 by @dominik-zeglen
|
- Send address update mutation only once - #210 by @dominik-zeglen
|
||||||
- Update sale details design - #207 by @dominik-zeglen
|
- Update sale details design - #207 by @dominik-zeglen
|
||||||
|
- Improve autocomplete UX - #212 by @dominik-zeglen
|
||||||
|
|
3
assets/images/ChevronDown.svg
Normal file
3
assets/images/ChevronDown.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.23278 6.21084L13 -6.40628e-08L14.4656 1.3609L7.23278 9.15006L-1.15036e-05 1.3609L1.46558 -5.68248e-07L7.23278 6.21084Z" fill="#06847B"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 292 B |
|
@ -1,6 +1,6 @@
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"POT-Creation-Date: 2019-10-15T15:56:00.137Z\n"
|
"POT-Creation-Date: 2019-10-16T15:55:00.250Z\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -267,15 +267,11 @@ msgctxt "description"
|
||||||
msgid "Add new menu item to begin creating menu"
|
msgid "Add new menu item to begin creating menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build/locale/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.json
|
#: build/locale/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectFieldContent.json
|
||||||
#. [src.components.MultiAutocompleteSelectField.1477537381] - add custom option to select input
|
#. [src.components.MultiAutocompleteSelectField.1477537381] - add custom select input option
|
||||||
#. defaultMessage is:
|
#. defaultMessage is:
|
||||||
#. Add new value: {value}
|
#. Add new value: {value}
|
||||||
msgctxt "add custom option to select input"
|
#: build/locale/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent.json
|
||||||
msgid "Add new value: {value}"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: build/locale/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.json
|
|
||||||
#. [src.components.SingleAutocompleteSelectField.1477537381] - add custom select input option
|
#. [src.components.SingleAutocompleteSelectField.1477537381] - add custom select input option
|
||||||
#. defaultMessage is:
|
#. defaultMessage is:
|
||||||
#. Add new value: {value}
|
#. Add new value: {value}
|
||||||
|
@ -3411,14 +3407,6 @@ msgctxt "dialog header"
|
||||||
msgid "Edit Billing Address"
|
msgid "Edit Billing Address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build/locale/src/orders/components/OrderCustomerEditDialog/OrderCustomerEditDialog.json
|
|
||||||
#. [src.orders.components.OrderCustomerEditDialog.1549172886] - dialog header
|
|
||||||
#. defaultMessage is:
|
|
||||||
#. Edit Customer Details
|
|
||||||
msgctxt "dialog header"
|
|
||||||
msgid "Edit Customer Details"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: build/locale/src/navigation/components/MenuItemDialog/MenuItemDialog.json
|
#: build/locale/src/navigation/components/MenuItemDialog/MenuItemDialog.json
|
||||||
#. [menuItemDialogEditItem] - edit menu item, header
|
#. [menuItemDialogEditItem] - edit menu item, header
|
||||||
#. defaultMessage is:
|
#. defaultMessage is:
|
||||||
|
@ -4863,7 +4851,7 @@ msgctxt "description"
|
||||||
msgid "No results"
|
msgid "No results"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build/locale/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.json
|
#: build/locale/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectFieldContent.json
|
||||||
#. [src.components.MultiAutocompleteSelectField.4205644805]
|
#. [src.components.MultiAutocompleteSelectField.4205644805]
|
||||||
#. defaultMessage is:
|
#. defaultMessage is:
|
||||||
#. No results found
|
#. No results found
|
||||||
|
@ -4875,7 +4863,7 @@ msgstr ""
|
||||||
#. [src.components.RadioGroupField.4205644805]
|
#. [src.components.RadioGroupField.4205644805]
|
||||||
#. defaultMessage is:
|
#. defaultMessage is:
|
||||||
#. No results found
|
#. No results found
|
||||||
#: build/locale/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.json
|
#: build/locale/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent.json
|
||||||
#. [src.components.SingleAutocompleteSelectField.4205644805]
|
#. [src.components.SingleAutocompleteSelectField.4205644805]
|
||||||
#. defaultMessage is:
|
#. defaultMessage is:
|
||||||
#. No results found
|
#. No results found
|
||||||
|
@ -5047,7 +5035,7 @@ msgctxt "description"
|
||||||
msgid "No. of Products"
|
msgid "No. of Products"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build/locale/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.json
|
#: build/locale/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent.json
|
||||||
#. [src.components.SingleAutocompleteSelectField.3069107721]
|
#. [src.components.SingleAutocompleteSelectField.3069107721]
|
||||||
#. defaultMessage is:
|
#. defaultMessage is:
|
||||||
#. None
|
#. None
|
||||||
|
|
|
@ -62,7 +62,6 @@
|
||||||
"react-sortable-tree": "^2.6.2",
|
"react-sortable-tree": "^2.6.2",
|
||||||
"react-svg": "^2.2.11",
|
"react-svg": "^2.2.11",
|
||||||
"slugify": "^1.3.4",
|
"slugify": "^1.3.4",
|
||||||
"string-similarity": "^1.2.2",
|
|
||||||
"typescript": "^3.5.3",
|
"typescript": "^3.5.3",
|
||||||
"url-join": "^4.0.1",
|
"url-join": "^4.0.1",
|
||||||
"use-react-router": "^1.0.7"
|
"use-react-router": "^1.0.7"
|
||||||
|
@ -99,7 +98,6 @@
|
||||||
"@types/react-test-renderer": "^16.8.2",
|
"@types/react-test-renderer": "^16.8.2",
|
||||||
"@types/storybook__addon-storyshots": "^3.4.9",
|
"@types/storybook__addon-storyshots": "^3.4.9",
|
||||||
"@types/storybook__react": "^4.0.2",
|
"@types/storybook__react": "^4.0.2",
|
||||||
"@types/string-similarity": "^1.2.1",
|
|
||||||
"@types/url-join": "^0.8.3",
|
"@types/url-join": "^0.8.3",
|
||||||
"@types/webappsec-credential-management": "^0.5.1",
|
"@types/webappsec-credential-management": "^0.5.1",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
|
|
|
@ -299,7 +299,7 @@ export const CollectionDetails: React.StatelessComponent<
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
products={maybe(() =>
|
products={maybe(() =>
|
||||||
result.data.products.edges
|
result.data.search.edges
|
||||||
.map(edge => edge.node)
|
.map(edge => edge.node)
|
||||||
.filter(suggestedProduct => suggestedProduct.id)
|
.filter(suggestedProduct => suggestedProduct.id)
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -19,11 +19,11 @@ import ConfirmButton, {
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
import { SearchCategories_categories_edges_node } from "../../containers/SearchCategories/types/SearchCategories";
|
import { SearchCategories_search_edges_node } from "../../containers/SearchCategories/types/SearchCategories";
|
||||||
import Checkbox from "../Checkbox";
|
import Checkbox from "../Checkbox";
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
categories: SearchCategories_categories_edges_node[];
|
categories: SearchCategories_search_edges_node[];
|
||||||
query: string;
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,22 +45,20 @@ const styles = createStyles({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface AssignCategoriesDialogProps extends WithStyles<typeof styles> {
|
interface AssignCategoriesDialogProps extends WithStyles<typeof styles> {
|
||||||
categories: SearchCategories_categories_edges_node[];
|
categories: SearchCategories_search_edges_node[];
|
||||||
confirmButtonState: ConfirmButtonTransitionState;
|
confirmButtonState: ConfirmButtonTransitionState;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onFetch: (value: string) => void;
|
onFetch: (value: string) => void;
|
||||||
onSubmit: (data: SearchCategories_categories_edges_node[]) => void;
|
onSubmit: (data: SearchCategories_search_edges_node[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCategoryAssign(
|
function handleCategoryAssign(
|
||||||
product: SearchCategories_categories_edges_node,
|
product: SearchCategories_search_edges_node,
|
||||||
isSelected: boolean,
|
isSelected: boolean,
|
||||||
selectedCategories: SearchCategories_categories_edges_node[],
|
selectedCategories: SearchCategories_search_edges_node[],
|
||||||
setSelectedCategories: (
|
setSelectedCategories: (data: SearchCategories_search_edges_node[]) => void
|
||||||
data: SearchCategories_categories_edges_node[]
|
|
||||||
) => void
|
|
||||||
) {
|
) {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
setSelectedCategories(
|
setSelectedCategories(
|
||||||
|
@ -89,7 +87,7 @@ const AssignCategoriesDialog = withStyles(styles, {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||||
const [selectedCategories, setSelectedCategories] = React.useState<
|
const [selectedCategories, setSelectedCategories] = React.useState<
|
||||||
SearchCategories_categories_edges_node[]
|
SearchCategories_search_edges_node[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const handleSubmit = () => onSubmit(selectedCategories);
|
const handleSubmit = () => onSubmit(selectedCategories);
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
import { SearchCollections_collections_edges_node } from "../../containers/SearchCollections/types/SearchCollections";
|
import { SearchCollections_search_edges_node } from "../../containers/SearchCollections/types/SearchCollections";
|
||||||
import Checkbox from "../Checkbox";
|
import Checkbox from "../Checkbox";
|
||||||
import ConfirmButton, {
|
import ConfirmButton, {
|
||||||
ConfirmButtonTransitionState
|
ConfirmButtonTransitionState
|
||||||
|
@ -23,7 +23,7 @@ import ConfirmButton, {
|
||||||
import FormSpacer from "../FormSpacer";
|
import FormSpacer from "../FormSpacer";
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
collections: SearchCollections_collections_edges_node[];
|
collections: SearchCollections_search_edges_node[];
|
||||||
query: string;
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,22 +45,20 @@ const styles = createStyles({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface AssignCollectionDialogProps extends WithStyles<typeof styles> {
|
interface AssignCollectionDialogProps extends WithStyles<typeof styles> {
|
||||||
collections: SearchCollections_collections_edges_node[];
|
collections: SearchCollections_search_edges_node[];
|
||||||
confirmButtonState: ConfirmButtonTransitionState;
|
confirmButtonState: ConfirmButtonTransitionState;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onFetch: (value: string) => void;
|
onFetch: (value: string) => void;
|
||||||
onSubmit: (data: SearchCollections_collections_edges_node[]) => void;
|
onSubmit: (data: SearchCollections_search_edges_node[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCollectionAssign(
|
function handleCollectionAssign(
|
||||||
product: SearchCollections_collections_edges_node,
|
product: SearchCollections_search_edges_node,
|
||||||
isSelected: boolean,
|
isSelected: boolean,
|
||||||
selectedCollections: SearchCollections_collections_edges_node[],
|
selectedCollections: SearchCollections_search_edges_node[],
|
||||||
setSelectedCollections: (
|
setSelectedCollections: (data: SearchCollections_search_edges_node[]) => void
|
||||||
data: SearchCollections_collections_edges_node[]
|
|
||||||
) => void
|
|
||||||
) {
|
) {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
setSelectedCollections(
|
setSelectedCollections(
|
||||||
|
@ -89,7 +87,7 @@ const AssignCollectionDialog = withStyles(styles, {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||||
const [selectedCollections, setSelectedCollections] = React.useState<
|
const [selectedCollections, setSelectedCollections] = React.useState<
|
||||||
SearchCollections_collections_edges_node[]
|
SearchCollections_search_edges_node[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const handleSubmit = () => onSubmit(selectedCollections);
|
const handleSubmit = () => onSubmit(selectedCollections);
|
||||||
|
|
|
@ -21,11 +21,11 @@ import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import { SearchProducts_products_edges_node } from "../../containers/SearchProducts/types/SearchProducts";
|
import { SearchProducts_search_edges_node } from "../../containers/SearchProducts/types/SearchProducts";
|
||||||
import Checkbox from "../Checkbox";
|
import Checkbox from "../Checkbox";
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
products: SearchProducts_products_edges_node[];
|
products: SearchProducts_search_edges_node[];
|
||||||
query: string;
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,18 +53,18 @@ const styles = createStyles({
|
||||||
export interface AssignProductDialogProps {
|
export interface AssignProductDialogProps {
|
||||||
confirmButtonState: ConfirmButtonTransitionState;
|
confirmButtonState: ConfirmButtonTransitionState;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
products: SearchProducts_products_edges_node[];
|
products: SearchProducts_search_edges_node[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onFetch: (value: string) => void;
|
onFetch: (value: string) => void;
|
||||||
onSubmit: (data: SearchProducts_products_edges_node[]) => void;
|
onSubmit: (data: SearchProducts_search_edges_node[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleProductAssign(
|
function handleProductAssign(
|
||||||
product: SearchProducts_products_edges_node,
|
product: SearchProducts_search_edges_node,
|
||||||
isSelected: boolean,
|
isSelected: boolean,
|
||||||
selectedProducts: SearchProducts_products_edges_node[],
|
selectedProducts: SearchProducts_search_edges_node[],
|
||||||
setSelectedProducts: (data: SearchProducts_products_edges_node[]) => void
|
setSelectedProducts: (data: SearchProducts_search_edges_node[]) => void
|
||||||
) {
|
) {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
setSelectedProducts(
|
setSelectedProducts(
|
||||||
|
@ -93,7 +93,7 @@ const AssignProductDialog = withStyles(styles, {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||||
const [selectedProducts, setSelectedProducts] = React.useState<
|
const [selectedProducts, setSelectedProducts] = React.useState<
|
||||||
SearchProducts_products_edges_node[]
|
SearchProducts_search_edges_node[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const handleSubmit = () => onSubmit(selectedProducts);
|
const handleSubmit = () => onSubmit(selectedProducts);
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { countries } from "@saleor/fixtures";
|
||||||
|
import useMultiAutocomplete from "@saleor/hooks/useMultiAutocomplete";
|
||||||
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
|
import { ChoiceProvider } from "@saleor/storybook/mock";
|
||||||
|
import MultiAutocompleteSelectField, {
|
||||||
|
MultiAutocompleteSelectFieldProps
|
||||||
|
} from "./MultiAutocompleteSelectField";
|
||||||
|
import MultiAutocompleteSelectFieldContent, {
|
||||||
|
MultiAutocompleteSelectFieldContentProps
|
||||||
|
} from "./MultiAutocompleteSelectFieldContent";
|
||||||
|
|
||||||
|
const suggestions = countries.map(c => ({ label: c.name, value: c.code }));
|
||||||
|
|
||||||
|
const props: MultiAutocompleteSelectFieldProps = {
|
||||||
|
choices: undefined,
|
||||||
|
displayValues: [],
|
||||||
|
label: "Country",
|
||||||
|
loading: false,
|
||||||
|
name: "country",
|
||||||
|
onChange: () => undefined,
|
||||||
|
placeholder: "Select country",
|
||||||
|
value: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
const Story: React.FC<
|
||||||
|
Partial<
|
||||||
|
MultiAutocompleteSelectFieldProps & {
|
||||||
|
enableLoadMore: boolean;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
> = ({ allowCustomValues, enableLoadMore }) => {
|
||||||
|
const { change, data: countries } = useMultiAutocomplete([suggestions[0]]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChoiceProvider choices={suggestions}>
|
||||||
|
{({ choices, fetchChoices, fetchMore, hasMore, loading }) => (
|
||||||
|
<MultiAutocompleteSelectField
|
||||||
|
{...props}
|
||||||
|
displayValues={countries}
|
||||||
|
choices={choices}
|
||||||
|
fetchChoices={fetchChoices}
|
||||||
|
helperText={`Value: ${countries
|
||||||
|
.map(country => country.value)
|
||||||
|
.join(", ")}`}
|
||||||
|
onChange={event => change(event, choices)}
|
||||||
|
value={countries.map(country => country.value)}
|
||||||
|
loading={loading}
|
||||||
|
hasMore={enableLoadMore ? hasMore : false}
|
||||||
|
onFetchMore={enableLoadMore ? fetchMore : undefined}
|
||||||
|
allowCustomValues={allowCustomValues}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ChoiceProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentProps: MultiAutocompleteSelectFieldContentProps = {
|
||||||
|
choices: suggestions.slice(0, 10),
|
||||||
|
displayCustomValue: false,
|
||||||
|
displayValues: [suggestions[0]],
|
||||||
|
getItemProps: () => undefined,
|
||||||
|
hasMore: false,
|
||||||
|
highlightedIndex: 0,
|
||||||
|
inputValue: suggestions[0].label,
|
||||||
|
loading: false,
|
||||||
|
onFetchMore: () => undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Generics / Multiple select with autocomplete", module)
|
||||||
|
.addDecorator(CardDecorator)
|
||||||
|
.addDecorator(Decorator)
|
||||||
|
.add("default", () => (
|
||||||
|
<MultiAutocompleteSelectFieldContent {...contentProps} />
|
||||||
|
))
|
||||||
|
.add("can load more", () => (
|
||||||
|
<MultiAutocompleteSelectFieldContent {...contentProps} hasMore={true} />
|
||||||
|
))
|
||||||
|
.add("no data", () => (
|
||||||
|
<MultiAutocompleteSelectFieldContent {...contentProps} choices={[]} />
|
||||||
|
))
|
||||||
|
.add("interactive", () => <Story />)
|
||||||
|
.add("interactive with custom option", () => (
|
||||||
|
<Story allowCustomValues={true} />
|
||||||
|
))
|
||||||
|
.add("interactive with load more", () => <Story enableLoadMore={true} />);
|
|
@ -1,7 +1,4 @@
|
||||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
import MenuItem from "@material-ui/core/MenuItem";
|
|
||||||
import Paper from "@material-ui/core/Paper";
|
|
||||||
import {
|
import {
|
||||||
createStyles,
|
createStyles,
|
||||||
Theme,
|
Theme,
|
||||||
|
@ -12,27 +9,19 @@ import TextField from "@material-ui/core/TextField";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import CloseIcon from "@material-ui/icons/Close";
|
import CloseIcon from "@material-ui/icons/Close";
|
||||||
import Downshift, { ControllerStateAndHelpers } from "downshift";
|
import Downshift, { ControllerStateAndHelpers } from "downshift";
|
||||||
|
import { filter } from "fuzzaldrin";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
import { compareTwoStrings } from "string-similarity";
|
|
||||||
|
|
||||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||||
import Checkbox from "@saleor/components/Checkbox";
|
|
||||||
import Debounce, { DebounceProps } from "@saleor/components/Debounce";
|
import Debounce, { DebounceProps } from "@saleor/components/Debounce";
|
||||||
import ArrowDropdownIcon from "@saleor/icons/ArrowDropdown";
|
import ArrowDropdownIcon from "@saleor/icons/ArrowDropdown";
|
||||||
import Hr from "../Hr";
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
|
import MultiAutocompleteSelectFieldContent, {
|
||||||
export interface MultiAutocompleteChoiceType {
|
MultiAutocompleteChoiceType
|
||||||
label: string;
|
} from "./MultiAutocompleteSelectFieldContent";
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
checkbox: {
|
|
||||||
height: 24,
|
|
||||||
width: 20
|
|
||||||
},
|
|
||||||
chip: {
|
chip: {
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
|
@ -66,49 +55,11 @@ const styles = (theme: Theme) =>
|
||||||
container: {
|
container: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
position: "relative"
|
position: "relative"
|
||||||
},
|
|
||||||
hr: {
|
|
||||||
margin: `${theme.spacing.unit}px 0`
|
|
||||||
},
|
|
||||||
menuItem: {
|
|
||||||
"&:focus": {
|
|
||||||
backgroundColor: [
|
|
||||||
theme.palette.background.default,
|
|
||||||
"!important"
|
|
||||||
] as any,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
fontWeight: 400
|
|
||||||
},
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: [
|
|
||||||
theme.palette.background.default,
|
|
||||||
"!important"
|
|
||||||
] as any,
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
fontWeight: 700
|
|
||||||
},
|
|
||||||
borderRadius: 4,
|
|
||||||
display: "grid",
|
|
||||||
gridColumnGap: theme.spacing.unit + "px",
|
|
||||||
gridTemplateColumns: "30px 1fr",
|
|
||||||
height: "auto",
|
|
||||||
padding: 0,
|
|
||||||
whiteSpace: "normal"
|
|
||||||
},
|
|
||||||
menuItemLabel: {
|
|
||||||
overflowWrap: "break-word"
|
|
||||||
},
|
|
||||||
paper: {
|
|
||||||
left: 0,
|
|
||||||
marginTop: theme.spacing.unit,
|
|
||||||
padding: theme.spacing.unit,
|
|
||||||
position: "absolute",
|
|
||||||
right: 0,
|
|
||||||
zIndex: 2
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface MultiAutocompleteSelectFieldProps {
|
export interface MultiAutocompleteSelectFieldProps
|
||||||
|
extends Partial<FetchMoreProps> {
|
||||||
allowCustomValues?: boolean;
|
allowCustomValues?: boolean;
|
||||||
displayValues: MultiAutocompleteChoiceType[];
|
displayValues: MultiAutocompleteChoiceType[];
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -134,6 +85,7 @@ export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
choices,
|
choices,
|
||||||
classes,
|
classes,
|
||||||
displayValues,
|
displayValues,
|
||||||
|
hasMore,
|
||||||
helperText,
|
helperText,
|
||||||
label,
|
label,
|
||||||
loading,
|
loading,
|
||||||
|
@ -142,6 +94,7 @@ export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
value,
|
value,
|
||||||
fetchChoices,
|
fetchChoices,
|
||||||
onChange,
|
onChange,
|
||||||
|
onFetchMore,
|
||||||
...props
|
...props
|
||||||
}: MultiAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
|
}: MultiAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
|
||||||
const handleSelect = (
|
const handleSelect = (
|
||||||
|
@ -155,7 +108,6 @@ export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
target: { name, value: item }
|
target: { name, value: item }
|
||||||
} as any);
|
} as any);
|
||||||
};
|
};
|
||||||
const suggestions = choices.filter(choice => !value.includes(choice.value));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -171,123 +123,53 @@ export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
toggleMenu,
|
toggleMenu,
|
||||||
highlightedIndex,
|
highlightedIndex,
|
||||||
inputValue
|
inputValue
|
||||||
}) => (
|
}) => {
|
||||||
<div className={classes.container} {...props}>
|
const displayCustomValue =
|
||||||
<TextField
|
inputValue &&
|
||||||
InputProps={{
|
inputValue.length > 0 &&
|
||||||
...getInputProps({
|
allowCustomValues &&
|
||||||
placeholder
|
!choices.find(
|
||||||
}),
|
choice =>
|
||||||
endAdornment: (
|
choice.label.toLowerCase() === inputValue.toLowerCase()
|
||||||
<div>
|
);
|
||||||
{loading ? (
|
|
||||||
<CircularProgress size={20} />
|
return (
|
||||||
) : (
|
<div className={classes.container} {...props}>
|
||||||
|
<TextField
|
||||||
|
InputProps={{
|
||||||
|
...getInputProps({
|
||||||
|
placeholder
|
||||||
|
}),
|
||||||
|
endAdornment: (
|
||||||
|
<div>
|
||||||
<ArrowDropdownIcon onClick={toggleMenu} />
|
<ArrowDropdownIcon onClick={toggleMenu} />
|
||||||
)}
|
</div>
|
||||||
</div>
|
),
|
||||||
),
|
id: undefined,
|
||||||
id: undefined,
|
onClick: toggleMenu
|
||||||
onClick: toggleMenu
|
}}
|
||||||
}}
|
helperText={helperText}
|
||||||
helperText={helperText}
|
label={label}
|
||||||
label={label}
|
fullWidth={true}
|
||||||
fullWidth={true}
|
/>
|
||||||
/>
|
{isOpen && (!!inputValue || !!choices.length) && (
|
||||||
{isOpen && (!!inputValue || !!choices.length) && (
|
<MultiAutocompleteSelectFieldContent
|
||||||
<Paper className={classes.paper} square>
|
choices={choices.filter(
|
||||||
{choices.length > 0 ||
|
choice => !value.includes(choice.value)
|
||||||
displayValues.length > 0 ||
|
)}
|
||||||
allowCustomValues ? (
|
displayCustomValue={displayCustomValue}
|
||||||
<>
|
displayValues={displayValues}
|
||||||
{displayValues.map(value => (
|
getItemProps={getItemProps}
|
||||||
<MenuItem
|
hasMore={hasMore}
|
||||||
className={classes.menuItem}
|
highlightedIndex={highlightedIndex}
|
||||||
key={value.value}
|
loading={loading}
|
||||||
selected={true}
|
inputValue={inputValue}
|
||||||
component="div"
|
onFetchMore={onFetchMore}
|
||||||
{...getItemProps({
|
/>
|
||||||
item: value.value
|
)}
|
||||||
})}
|
</div>
|
||||||
data-tc="multiautocomplete-select-option"
|
);
|
||||||
>
|
}}
|
||||||
<Checkbox
|
|
||||||
className={classes.checkbox}
|
|
||||||
checked={true}
|
|
||||||
disableRipple
|
|
||||||
/>
|
|
||||||
<span className={classes.menuItemLabel}>
|
|
||||||
{value.label}
|
|
||||||
</span>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
{displayValues.length > 0 && suggestions.length > 0 && (
|
|
||||||
<Hr className={classes.hr} />
|
|
||||||
)}
|
|
||||||
{suggestions.map((suggestion, index) => (
|
|
||||||
<MenuItem
|
|
||||||
className={classes.menuItem}
|
|
||||||
key={suggestion.value}
|
|
||||||
selected={highlightedIndex === index + value.length}
|
|
||||||
component="div"
|
|
||||||
{...getItemProps({
|
|
||||||
item: suggestion.value
|
|
||||||
})}
|
|
||||||
data-tc="multiautocomplete-select-option"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={value.includes(suggestion.value)}
|
|
||||||
className={classes.checkbox}
|
|
||||||
disableRipple
|
|
||||||
/>
|
|
||||||
<span className={classes.menuItemLabel}>
|
|
||||||
{suggestion.label}
|
|
||||||
</span>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
{allowCustomValues &&
|
|
||||||
inputValue &&
|
|
||||||
!choices.find(
|
|
||||||
choice =>
|
|
||||||
choice.label.toLowerCase() ===
|
|
||||||
inputValue.toLowerCase()
|
|
||||||
) && (
|
|
||||||
<MenuItem
|
|
||||||
className={classes.menuItem}
|
|
||||||
key={"customValue"}
|
|
||||||
component="div"
|
|
||||||
{...getItemProps({
|
|
||||||
item: inputValue
|
|
||||||
})}
|
|
||||||
data-tc="multiautocomplete-select-option"
|
|
||||||
>
|
|
||||||
<span className={classes.menuItemLabel}>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Add new value: {value}"
|
|
||||||
description="add custom option to select input"
|
|
||||||
values={{
|
|
||||||
value: inputValue
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
!loading && (
|
|
||||||
<MenuItem
|
|
||||||
disabled={true}
|
|
||||||
component="div"
|
|
||||||
data-tc="multiautocomplete-select-no-options"
|
|
||||||
>
|
|
||||||
<FormattedMessage defaultMessage="No results found" />
|
|
||||||
</MenuItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Downshift>
|
</Downshift>
|
||||||
<div className={classes.chipContainer}>
|
<div className={classes.chipContainer}>
|
||||||
{displayValues.map(value => (
|
{displayValues.map(value => (
|
||||||
|
@ -328,22 +210,12 @@ const MultiAutocompleteSelectField: React.FC<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedChoices = choices.sort((a, b) => {
|
|
||||||
const ratingA = compareTwoStrings(query, a.label);
|
|
||||||
const ratingB = compareTwoStrings(query, b.label);
|
|
||||||
if (ratingA > ratingB) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (ratingA < ratingB) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiAutocompleteSelectFieldComponent
|
<MultiAutocompleteSelectFieldComponent
|
||||||
fetchChoices={q => setQuery(q || "")}
|
fetchChoices={q => setQuery(q || "")}
|
||||||
choices={sortedChoices}
|
choices={filter(choices, query, {
|
||||||
|
key: "label"
|
||||||
|
})}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,293 @@
|
||||||
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
|
import MenuItem from "@material-ui/core/MenuItem";
|
||||||
|
import Paper from "@material-ui/core/Paper";
|
||||||
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
import AddIcon from "@material-ui/icons/Add";
|
||||||
|
import { makeStyles } from "@material-ui/styles";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { GetItemPropsOptions } from "downshift";
|
||||||
|
import React from "react";
|
||||||
|
import SVG from "react-inlinesvg";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import chevronDown from "@assets/images/ChevronDown.svg";
|
||||||
|
import Checkbox from "@saleor/components/Checkbox";
|
||||||
|
import useElementScroll, {
|
||||||
|
isScrolledToBottom
|
||||||
|
} from "@saleor/hooks/useElementScroll";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
|
import Hr from "../Hr";
|
||||||
|
|
||||||
|
const menuItemHeight = 46;
|
||||||
|
const maxMenuItems = 5;
|
||||||
|
const offset = 24;
|
||||||
|
|
||||||
|
export interface MultiAutocompleteChoiceType {
|
||||||
|
label: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
export interface MultiAutocompleteSelectFieldContentProps
|
||||||
|
extends Partial<FetchMoreProps> {
|
||||||
|
choices: MultiAutocompleteChoiceType[];
|
||||||
|
displayCustomValue: boolean;
|
||||||
|
displayValues: MultiAutocompleteChoiceType[];
|
||||||
|
getItemProps: (options: GetItemPropsOptions) => void;
|
||||||
|
highlightedIndex: number;
|
||||||
|
inputValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
(theme: Theme) => ({
|
||||||
|
addIcon: {
|
||||||
|
height: 24,
|
||||||
|
margin: 9,
|
||||||
|
width: 20
|
||||||
|
},
|
||||||
|
arrowContainer: {
|
||||||
|
position: "relative"
|
||||||
|
},
|
||||||
|
arrowInnerContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
background: theme.palette.grey[50],
|
||||||
|
bottom: 0,
|
||||||
|
display: "flex",
|
||||||
|
height: 30,
|
||||||
|
justifyContent: "center",
|
||||||
|
opacity: 1,
|
||||||
|
position: "absolute",
|
||||||
|
transition: theme.transitions.duration.short + "ms",
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
checkbox: {
|
||||||
|
height: 24,
|
||||||
|
width: 20
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
maxHeight: menuItemHeight * maxMenuItems + theme.spacing.unit * 2,
|
||||||
|
overflow: "scroll",
|
||||||
|
padding: 8
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
opacity: 0
|
||||||
|
},
|
||||||
|
hr: {
|
||||||
|
margin: `${theme.spacing.unit}px 0`
|
||||||
|
},
|
||||||
|
menuItem: {
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: [
|
||||||
|
theme.palette.background.default,
|
||||||
|
"!important"
|
||||||
|
] as any,
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontWeight: 400
|
||||||
|
},
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: [
|
||||||
|
theme.palette.background.default,
|
||||||
|
"!important"
|
||||||
|
] as any,
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontWeight: 700
|
||||||
|
},
|
||||||
|
borderRadius: 4,
|
||||||
|
display: "grid",
|
||||||
|
gridColumnGap: theme.spacing.unit + "px",
|
||||||
|
gridTemplateColumns: "30px 1fr",
|
||||||
|
height: "auto",
|
||||||
|
padding: 0,
|
||||||
|
whiteSpace: "normal"
|
||||||
|
},
|
||||||
|
menuItemLabel: {
|
||||||
|
overflowWrap: "break-word"
|
||||||
|
},
|
||||||
|
progress: {},
|
||||||
|
progressContainer: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center"
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
borderBottomLeftRadius: 8,
|
||||||
|
borderBottomRightRadius: 8,
|
||||||
|
left: 0,
|
||||||
|
marginTop: theme.spacing.unit,
|
||||||
|
overflow: "hidden",
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
zIndex: 22
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "MultiAutocompleteSelectFieldContent"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function getChoiceIndex(
|
||||||
|
index: number,
|
||||||
|
displayValues: MultiAutocompleteChoiceType[],
|
||||||
|
displayCustomValue: boolean
|
||||||
|
) {
|
||||||
|
let choiceIndex = index;
|
||||||
|
if (displayCustomValue) {
|
||||||
|
choiceIndex += 2;
|
||||||
|
}
|
||||||
|
if (displayValues.length > 0) {
|
||||||
|
choiceIndex += 1 + displayValues.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return choiceIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MultiAutocompleteSelectFieldContent: React.FC<
|
||||||
|
MultiAutocompleteSelectFieldContentProps
|
||||||
|
> = props => {
|
||||||
|
const {
|
||||||
|
choices,
|
||||||
|
displayCustomValue,
|
||||||
|
displayValues,
|
||||||
|
getItemProps,
|
||||||
|
hasMore,
|
||||||
|
highlightedIndex,
|
||||||
|
loading,
|
||||||
|
inputValue,
|
||||||
|
onFetchMore
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const classes = useStyles(props);
|
||||||
|
const anchor = React.useRef<HTMLDivElement>();
|
||||||
|
const scrollPosition = useElementScroll(anchor);
|
||||||
|
const [calledForMore, setCalledForMore] = React.useState(false);
|
||||||
|
|
||||||
|
const scrolledToBottom = isScrolledToBottom(anchor, scrollPosition, offset);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!calledForMore && onFetchMore && scrolledToBottom) {
|
||||||
|
onFetchMore();
|
||||||
|
setCalledForMore(true);
|
||||||
|
}
|
||||||
|
}, [scrolledToBottom]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (calledForMore && !loading) {
|
||||||
|
setCalledForMore(false);
|
||||||
|
}
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper className={classes.root}>
|
||||||
|
<div className={classes.content} ref={anchor}>
|
||||||
|
{choices.length > 0 ||
|
||||||
|
displayValues.length > 0 ||
|
||||||
|
displayCustomValue ? (
|
||||||
|
<>
|
||||||
|
{displayCustomValue && (
|
||||||
|
<MenuItem
|
||||||
|
className={classes.menuItem}
|
||||||
|
key="customValue"
|
||||||
|
component="div"
|
||||||
|
{...getItemProps({
|
||||||
|
item: inputValue
|
||||||
|
})}
|
||||||
|
data-tc="multiautocomplete-select-option"
|
||||||
|
>
|
||||||
|
<AddIcon className={classes.addIcon} color="primary" />
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Add new value: {value}"
|
||||||
|
description="add custom select input option"
|
||||||
|
values={{
|
||||||
|
value: inputValue
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{(choices.length > 0 || displayValues.length > 0) &&
|
||||||
|
displayCustomValue && <Hr className={classes.hr} />}
|
||||||
|
{displayValues.map(value => (
|
||||||
|
<MenuItem
|
||||||
|
className={classes.menuItem}
|
||||||
|
key={value.value}
|
||||||
|
selected={true}
|
||||||
|
component="div"
|
||||||
|
{...getItemProps({
|
||||||
|
item: value.value
|
||||||
|
})}
|
||||||
|
data-tc="multiautocomplete-select-option"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
className={classes.checkbox}
|
||||||
|
checked={true}
|
||||||
|
disableRipple
|
||||||
|
/>
|
||||||
|
<span className={classes.menuItemLabel}>{value.label}</span>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
{displayValues.length > 0 && choices.length > 0 && (
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
)}
|
||||||
|
{choices.map((suggestion, index) => {
|
||||||
|
const choiceIndex = getChoiceIndex(
|
||||||
|
index,
|
||||||
|
displayValues,
|
||||||
|
displayCustomValue
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
className={classes.menuItem}
|
||||||
|
key={suggestion.value}
|
||||||
|
selected={highlightedIndex === choiceIndex}
|
||||||
|
component="div"
|
||||||
|
{...getItemProps({
|
||||||
|
index: choiceIndex,
|
||||||
|
item: suggestion.value
|
||||||
|
})}
|
||||||
|
data-tc="multiautocomplete-select-option"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={false}
|
||||||
|
className={classes.checkbox}
|
||||||
|
disableRipple
|
||||||
|
/>
|
||||||
|
<span className={classes.menuItemLabel}>
|
||||||
|
{suggestion.label}
|
||||||
|
</span>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{hasMore && (
|
||||||
|
<>
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
<div className={classes.progressContainer}>
|
||||||
|
<CircularProgress className={classes.progress} size={24} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<MenuItem
|
||||||
|
disabled={true}
|
||||||
|
component="div"
|
||||||
|
data-tc="multiautocomplete-select-no-options"
|
||||||
|
>
|
||||||
|
<FormattedMessage defaultMessage="No results found" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={classes.arrowContainer}>
|
||||||
|
<div
|
||||||
|
className={classNames(classes.arrowInnerContainer, {
|
||||||
|
// Needs to be explicitely compared to false because
|
||||||
|
// scrolledToBottom can be either true, false or undefined
|
||||||
|
[classes.hide]: scrolledToBottom !== false
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<SVG src={chevronDown} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MultiAutocompleteSelectFieldContent.displayName =
|
||||||
|
"MultiAutocompleteSelectFieldContent";
|
||||||
|
export default MultiAutocompleteSelectFieldContent;
|
|
@ -1,2 +1,3 @@
|
||||||
export { default } from "./MultiAutocompleteSelectField";
|
export { default } from "./MultiAutocompleteSelectField";
|
||||||
export * from "./MultiAutocompleteSelectField";
|
export * from "./MultiAutocompleteSelectField";
|
||||||
|
export * from "./MultiAutocompleteSelectFieldContent";
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import Form from "@saleor/components/Form";
|
||||||
|
import { countries } from "@saleor/fixtures";
|
||||||
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
|
import { ChoiceProvider } from "@saleor/storybook/mock";
|
||||||
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
|
import SingleAutocompleteSelectField, {
|
||||||
|
SingleAutocompleteSelectFieldProps
|
||||||
|
} from "./SingleAutocompleteSelectField";
|
||||||
|
import SingleAutocompleteSelectFieldContent, {
|
||||||
|
SingleAutocompleteSelectFieldContentProps
|
||||||
|
} from "./SingleAutocompleteSelectFieldContent";
|
||||||
|
|
||||||
|
const suggestions = countries.map(c => ({ label: c.name, value: c.code }));
|
||||||
|
|
||||||
|
const props: SingleAutocompleteSelectFieldProps = {
|
||||||
|
choices: undefined,
|
||||||
|
displayValue: undefined,
|
||||||
|
label: "Country",
|
||||||
|
loading: false,
|
||||||
|
name: "country",
|
||||||
|
onChange: () => undefined,
|
||||||
|
placeholder: "Select country",
|
||||||
|
value: suggestions[0].value
|
||||||
|
};
|
||||||
|
|
||||||
|
const Story: React.FC<
|
||||||
|
Partial<
|
||||||
|
SingleAutocompleteSelectFieldProps & {
|
||||||
|
enableLoadMore: boolean;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
> = ({ allowCustomValues, emptyOption, enableLoadMore }) => {
|
||||||
|
const [displayValue, setDisplayValue] = React.useState(suggestions[0].label);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form initial={{ country: suggestions[0].value }}>
|
||||||
|
{({ change, data }) => (
|
||||||
|
<ChoiceProvider choices={suggestions}>
|
||||||
|
{({ choices, fetchChoices, fetchMore, hasMore, loading }) => {
|
||||||
|
const handleSelect = createSingleAutocompleteSelectHandler(
|
||||||
|
change,
|
||||||
|
setDisplayValue,
|
||||||
|
choices
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SingleAutocompleteSelectField
|
||||||
|
{...props}
|
||||||
|
displayValue={displayValue}
|
||||||
|
choices={choices}
|
||||||
|
fetchChoices={fetchChoices}
|
||||||
|
helperText={`Value: ${data.country}`}
|
||||||
|
loading={loading}
|
||||||
|
onChange={handleSelect}
|
||||||
|
value={data.country}
|
||||||
|
hasMore={enableLoadMore ? hasMore : false}
|
||||||
|
onFetchMore={enableLoadMore ? fetchMore : undefined}
|
||||||
|
allowCustomValues={allowCustomValues}
|
||||||
|
emptyOption={emptyOption}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ChoiceProvider>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentProps: SingleAutocompleteSelectFieldContentProps = {
|
||||||
|
choices: suggestions.slice(0, 10),
|
||||||
|
displayCustomValue: false,
|
||||||
|
emptyOption: false,
|
||||||
|
getItemProps: () => undefined,
|
||||||
|
hasMore: false,
|
||||||
|
highlightedIndex: 0,
|
||||||
|
inputValue: suggestions[0].label,
|
||||||
|
isCustomValueSelected: false,
|
||||||
|
loading: false,
|
||||||
|
onFetchMore: () => undefined,
|
||||||
|
selectedItem: suggestions[0].value
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Generics / Select with autocomplete", module)
|
||||||
|
.addDecorator(CardDecorator)
|
||||||
|
.addDecorator(Decorator)
|
||||||
|
.add("default", () => (
|
||||||
|
<SingleAutocompleteSelectFieldContent {...contentProps} />
|
||||||
|
))
|
||||||
|
.add("can load more", () => (
|
||||||
|
<SingleAutocompleteSelectFieldContent {...contentProps} hasMore={true} />
|
||||||
|
))
|
||||||
|
.add("no data", () => (
|
||||||
|
<SingleAutocompleteSelectFieldContent {...contentProps} choices={[]} />
|
||||||
|
))
|
||||||
|
.add("interactive", () => <Story />)
|
||||||
|
.add("interactive with custom option", () => (
|
||||||
|
<Story allowCustomValues={true} />
|
||||||
|
))
|
||||||
|
.add("interactive with empty option", () => <Story emptyOption={true} />)
|
||||||
|
.add("interactive with load more", () => <Story enableLoadMore={true} />);
|
|
@ -1,59 +1,34 @@
|
||||||
import { Omit } from "@material-ui/core";
|
|
||||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
|
||||||
import { InputProps } from "@material-ui/core/Input";
|
import { InputProps } from "@material-ui/core/Input";
|
||||||
import MenuItem from "@material-ui/core/MenuItem";
|
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
||||||
import Paper from "@material-ui/core/Paper";
|
|
||||||
import {
|
|
||||||
createStyles,
|
|
||||||
Theme,
|
|
||||||
withStyles,
|
|
||||||
WithStyles
|
|
||||||
} from "@material-ui/core/styles";
|
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
import Typography from "@material-ui/core/Typography";
|
|
||||||
import Downshift from "downshift";
|
import Downshift from "downshift";
|
||||||
|
import { filter } from "fuzzaldrin";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import SingleAutocompleteSelectFieldContent, {
|
||||||
import { compareTwoStrings } from "string-similarity";
|
SingleAutocompleteChoiceType
|
||||||
|
} from "./SingleAutocompleteSelectFieldContent";
|
||||||
|
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
|
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
|
||||||
import Debounce, { DebounceProps } from "../Debounce";
|
import Debounce, { DebounceProps } from "../Debounce";
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = createStyles({
|
||||||
createStyles({
|
container: {
|
||||||
container: {
|
flexGrow: 1,
|
||||||
flexGrow: 1,
|
position: "relative"
|
||||||
position: "relative"
|
}
|
||||||
},
|
});
|
||||||
menuItem: {
|
|
||||||
height: "auto",
|
|
||||||
whiteSpace: "normal"
|
|
||||||
},
|
|
||||||
paper: {
|
|
||||||
borderRadius: 4,
|
|
||||||
left: 0,
|
|
||||||
marginTop: theme.spacing.unit,
|
|
||||||
padding: 8,
|
|
||||||
position: "absolute",
|
|
||||||
right: 0,
|
|
||||||
zIndex: 22
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface SingleAutocompleteChoiceType {
|
export interface SingleAutocompleteSelectFieldProps
|
||||||
label: string;
|
extends Partial<FetchMoreProps> {
|
||||||
value: any;
|
|
||||||
}
|
|
||||||
export interface SingleAutocompleteSelectFieldProps {
|
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
displayValue: string;
|
displayValue: string;
|
||||||
emptyOption?: boolean;
|
emptyOption?: boolean;
|
||||||
choices: SingleAutocompleteChoiceType[];
|
choices: SingleAutocompleteChoiceType[];
|
||||||
value?: string;
|
value: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
loading?: boolean;
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
allowCustomValues?: boolean;
|
allowCustomValues?: boolean;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
|
@ -63,13 +38,6 @@ export interface SingleAutocompleteSelectFieldProps {
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
onChange: (event: React.ChangeEvent<any>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SingleAutocompleteSelectFieldState {
|
|
||||||
choices: Array<{
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DebounceAutocomplete: React.ComponentType<
|
const DebounceAutocomplete: React.ComponentType<
|
||||||
DebounceProps<string>
|
DebounceProps<string>
|
||||||
> = Debounce;
|
> = Debounce;
|
||||||
|
@ -85,6 +53,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
displayValue,
|
displayValue,
|
||||||
emptyOption,
|
emptyOption,
|
||||||
error,
|
error,
|
||||||
|
hasMore,
|
||||||
helperText,
|
helperText,
|
||||||
label,
|
label,
|
||||||
loading,
|
loading,
|
||||||
|
@ -94,9 +63,11 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
InputProps,
|
InputProps,
|
||||||
fetchChoices,
|
fetchChoices,
|
||||||
onChange,
|
onChange,
|
||||||
|
onFetchMore,
|
||||||
...props
|
...props
|
||||||
}: SingleAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
|
}: SingleAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
|
||||||
const [prevDisplayValue] = useStateFromProps(displayValue);
|
const [prevDisplayValue] = useStateFromProps(displayValue);
|
||||||
|
|
||||||
const handleChange = item =>
|
const handleChange = item =>
|
||||||
onChange({
|
onChange({
|
||||||
target: {
|
target: {
|
||||||
|
@ -131,11 +102,21 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
choices && selectedItem
|
choices && selectedItem
|
||||||
? choices.filter(c => c.value === selectedItem).length === 0
|
? choices.filter(c => c.value === selectedItem).length === 0
|
||||||
: false;
|
: false;
|
||||||
|
const hasInputValueChanged = prevDisplayValue !== displayValue;
|
||||||
|
|
||||||
if (prevDisplayValue !== displayValue) {
|
if (hasInputValueChanged) {
|
||||||
reset({ inputValue: displayValue });
|
reset({ inputValue: displayValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const displayCustomValue =
|
||||||
|
inputValue &&
|
||||||
|
inputValue.length > 0 &&
|
||||||
|
allowCustomValues &&
|
||||||
|
!choices.find(
|
||||||
|
choice =>
|
||||||
|
choice.label.toLowerCase() === inputValue.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.container} {...props}>
|
<div className={classes.container} {...props}>
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -146,11 +127,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
}),
|
}),
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<div>
|
<div>
|
||||||
{loading ? (
|
<ArrowDropdownIcon onClick={toggleMenu} />
|
||||||
<CircularProgress size={20} />
|
|
||||||
) : (
|
|
||||||
<ArrowDropdownIcon onClick={toggleMenu} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
error,
|
error,
|
||||||
|
@ -165,82 +142,19 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
/>
|
/>
|
||||||
{isOpen && (!!inputValue || !!choices.length) && (
|
{isOpen && (!!inputValue || !!choices.length) && (
|
||||||
<Paper className={classes.paper} square>
|
<SingleAutocompleteSelectFieldContent
|
||||||
{choices.length > 0 || allowCustomValues ? (
|
choices={choices}
|
||||||
<>
|
displayCustomValue={displayCustomValue}
|
||||||
{emptyOption && (
|
emptyOption={emptyOption}
|
||||||
<MenuItem
|
getItemProps={getItemProps}
|
||||||
className={classes.menuItem}
|
hasMore={hasMore}
|
||||||
component="div"
|
highlightedIndex={highlightedIndex}
|
||||||
{...getItemProps({
|
loading={loading}
|
||||||
item: ""
|
inputValue={inputValue}
|
||||||
})}
|
isCustomValueSelected={isCustomValueSelected}
|
||||||
data-tc="singleautocomplete-select-option"
|
selectedItem={selectedItem}
|
||||||
>
|
onFetchMore={onFetchMore}
|
||||||
<Typography color="textSecondary">
|
/>
|
||||||
<FormattedMessage defaultMessage="None" />
|
|
||||||
</Typography>
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{choices.map((suggestion, index) => {
|
|
||||||
const choiceIndex = index + (emptyOption ? 1 : 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
className={classes.menuItem}
|
|
||||||
key={JSON.stringify(suggestion)}
|
|
||||||
selected={
|
|
||||||
highlightedIndex === choiceIndex ||
|
|
||||||
selectedItem === suggestion.value
|
|
||||||
}
|
|
||||||
component="div"
|
|
||||||
{...getItemProps({
|
|
||||||
index: choiceIndex,
|
|
||||||
item: suggestion.value
|
|
||||||
})}
|
|
||||||
data-tc="singleautocomplete-select-option"
|
|
||||||
>
|
|
||||||
{suggestion.label}
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{allowCustomValues &&
|
|
||||||
!!inputValue &&
|
|
||||||
!choices.find(
|
|
||||||
choice =>
|
|
||||||
choice.label.toLowerCase() ===
|
|
||||||
inputValue.toLowerCase()
|
|
||||||
) && (
|
|
||||||
<MenuItem
|
|
||||||
className={classes.menuItem}
|
|
||||||
key={"customValue"}
|
|
||||||
selected={isCustomValueSelected}
|
|
||||||
component="div"
|
|
||||||
{...getItemProps({
|
|
||||||
item: inputValue
|
|
||||||
})}
|
|
||||||
data-tc="singleautocomplete-select-option"
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Add new value: {value}"
|
|
||||||
description="add custom select input option"
|
|
||||||
values={{
|
|
||||||
value: inputValue
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<MenuItem
|
|
||||||
disabled={true}
|
|
||||||
component="div"
|
|
||||||
data-tc="singleautocomplete-select-no-options"
|
|
||||||
>
|
|
||||||
<FormattedMessage defaultMessage="No results found" />
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -252,40 +166,32 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export class SingleAutocompleteSelectField extends React.Component<
|
const SingleAutocompleteSelectField: React.FC<
|
||||||
Omit<SingleAutocompleteSelectFieldProps, "classes">,
|
SingleAutocompleteSelectFieldProps
|
||||||
SingleAutocompleteSelectFieldState
|
> = ({ choices, fetchChoices, ...props }) => {
|
||||||
> {
|
const [query, setQuery] = React.useState("");
|
||||||
state = { choices: this.props.choices };
|
if (fetchChoices) {
|
||||||
|
|
||||||
handleInputChange = (value: string) =>
|
|
||||||
this.setState((_, props) => ({
|
|
||||||
choices: props.choices
|
|
||||||
.sort((a, b) => {
|
|
||||||
const ratingA = compareTwoStrings(value || "", a.label);
|
|
||||||
const ratingB = compareTwoStrings(value || "", b.label);
|
|
||||||
if (ratingA > ratingB) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (ratingA < ratingB) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
})
|
|
||||||
.slice(0, 5)
|
|
||||||
}));
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!!this.props.fetchChoices) {
|
|
||||||
return <SingleAutocompleteSelectFieldComponent {...this.props} />;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<SingleAutocompleteSelectFieldComponent
|
<DebounceAutocomplete debounceFn={fetchChoices}>
|
||||||
{...this.props}
|
{debounceFn => (
|
||||||
choices={this.state.choices}
|
<SingleAutocompleteSelectFieldComponent
|
||||||
fetchChoices={this.handleInputChange}
|
choices={choices}
|
||||||
/>
|
{...props}
|
||||||
|
fetchChoices={debounceFn}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DebounceAutocomplete>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<SingleAutocompleteSelectFieldComponent
|
||||||
|
fetchChoices={q => setQuery(q || "")}
|
||||||
|
choices={filter(choices, query, {
|
||||||
|
key: "label"
|
||||||
|
})}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
export default SingleAutocompleteSelectField;
|
export default SingleAutocompleteSelectField;
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
|
import MenuItem from "@material-ui/core/MenuItem";
|
||||||
|
import Paper from "@material-ui/core/Paper";
|
||||||
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { makeStyles } from "@material-ui/styles";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { GetItemPropsOptions } from "downshift";
|
||||||
|
import React from "react";
|
||||||
|
import SVG from "react-inlinesvg";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import chevronDown from "@assets/images/ChevronDown.svg";
|
||||||
|
import useElementScroll, {
|
||||||
|
isScrolledToBottom
|
||||||
|
} from "@saleor/hooks/useElementScroll";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
|
import Hr from "../Hr";
|
||||||
|
|
||||||
|
const menuItemHeight = 46;
|
||||||
|
const maxMenuItems = 5;
|
||||||
|
const offset = 24;
|
||||||
|
|
||||||
|
export interface SingleAutocompleteChoiceType {
|
||||||
|
label: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
export interface SingleAutocompleteSelectFieldContentProps
|
||||||
|
extends Partial<FetchMoreProps> {
|
||||||
|
choices: SingleAutocompleteChoiceType[];
|
||||||
|
displayCustomValue: boolean;
|
||||||
|
emptyOption: boolean;
|
||||||
|
getItemProps: (options: GetItemPropsOptions) => void;
|
||||||
|
highlightedIndex: number;
|
||||||
|
inputValue: string;
|
||||||
|
isCustomValueSelected: boolean;
|
||||||
|
selectedItem: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
(theme: Theme) => ({
|
||||||
|
arrowContainer: {
|
||||||
|
position: "relative"
|
||||||
|
},
|
||||||
|
arrowInnerContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
background: theme.palette.grey[50],
|
||||||
|
bottom: 0,
|
||||||
|
display: "flex",
|
||||||
|
height: 30,
|
||||||
|
justifyContent: "center",
|
||||||
|
opacity: 1,
|
||||||
|
position: "absolute",
|
||||||
|
transition: theme.transitions.duration.short + "ms",
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
maxHeight: menuItemHeight * maxMenuItems + theme.spacing.unit * 2,
|
||||||
|
overflow: "scroll",
|
||||||
|
padding: 8
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
opacity: 0
|
||||||
|
},
|
||||||
|
hr: {
|
||||||
|
margin: `${theme.spacing.unit}px 0`
|
||||||
|
},
|
||||||
|
menuItem: {
|
||||||
|
height: "auto",
|
||||||
|
whiteSpace: "normal"
|
||||||
|
},
|
||||||
|
progress: {},
|
||||||
|
progressContainer: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center"
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
borderBottomLeftRadius: 8,
|
||||||
|
borderBottomRightRadius: 8,
|
||||||
|
left: 0,
|
||||||
|
marginTop: theme.spacing.unit,
|
||||||
|
overflow: "hidden",
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
zIndex: 22
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "SingleAutocompleteSelectFieldContent"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function getChoiceIndex(
|
||||||
|
index: number,
|
||||||
|
emptyValue: boolean,
|
||||||
|
customValue: boolean
|
||||||
|
) {
|
||||||
|
let choiceIndex = index;
|
||||||
|
if (emptyValue) {
|
||||||
|
choiceIndex += 1;
|
||||||
|
}
|
||||||
|
if (customValue) {
|
||||||
|
choiceIndex += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return choiceIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SingleAutocompleteSelectFieldContent: React.FC<
|
||||||
|
SingleAutocompleteSelectFieldContentProps
|
||||||
|
> = props => {
|
||||||
|
const {
|
||||||
|
choices,
|
||||||
|
displayCustomValue,
|
||||||
|
emptyOption,
|
||||||
|
getItemProps,
|
||||||
|
hasMore,
|
||||||
|
highlightedIndex,
|
||||||
|
loading,
|
||||||
|
inputValue,
|
||||||
|
isCustomValueSelected,
|
||||||
|
selectedItem,
|
||||||
|
onFetchMore
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const classes = useStyles(props);
|
||||||
|
const anchor = React.useRef<HTMLDivElement>();
|
||||||
|
const scrollPosition = useElementScroll(anchor);
|
||||||
|
const [calledForMore, setCalledForMore] = React.useState(false);
|
||||||
|
|
||||||
|
const scrolledToBottom = isScrolledToBottom(anchor, scrollPosition, offset);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!calledForMore && onFetchMore && scrolledToBottom) {
|
||||||
|
onFetchMore();
|
||||||
|
setCalledForMore(true);
|
||||||
|
}
|
||||||
|
}, [scrolledToBottom]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (calledForMore && !loading) {
|
||||||
|
setCalledForMore(false);
|
||||||
|
}
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper className={classes.root}>
|
||||||
|
<div className={classes.content} ref={anchor}>
|
||||||
|
{choices.length > 0 || displayCustomValue ? (
|
||||||
|
<>
|
||||||
|
{emptyOption && (
|
||||||
|
<MenuItem
|
||||||
|
className={classes.menuItem}
|
||||||
|
component="div"
|
||||||
|
{...getItemProps({
|
||||||
|
item: ""
|
||||||
|
})}
|
||||||
|
data-tc="singleautocomplete-select-option"
|
||||||
|
>
|
||||||
|
<Typography color="textSecondary">
|
||||||
|
<FormattedMessage defaultMessage="None" />
|
||||||
|
</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{displayCustomValue && (
|
||||||
|
<MenuItem
|
||||||
|
className={classes.menuItem}
|
||||||
|
key={"customValue"}
|
||||||
|
selected={isCustomValueSelected}
|
||||||
|
component="div"
|
||||||
|
{...getItemProps({
|
||||||
|
item: inputValue
|
||||||
|
})}
|
||||||
|
data-tc="singleautocomplete-select-option"
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Add new value: {value}"
|
||||||
|
description="add custom select input option"
|
||||||
|
values={{
|
||||||
|
value: inputValue
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{choices.length > 0 && displayCustomValue && (
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
)}
|
||||||
|
{choices.map((suggestion, index) => {
|
||||||
|
const choiceIndex = getChoiceIndex(
|
||||||
|
index,
|
||||||
|
emptyOption,
|
||||||
|
displayCustomValue
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
className={classes.menuItem}
|
||||||
|
key={JSON.stringify(suggestion)}
|
||||||
|
selected={
|
||||||
|
highlightedIndex === choiceIndex ||
|
||||||
|
selectedItem === suggestion.value
|
||||||
|
}
|
||||||
|
component="div"
|
||||||
|
{...getItemProps({
|
||||||
|
index: choiceIndex,
|
||||||
|
item: suggestion.value
|
||||||
|
})}
|
||||||
|
data-tc="singleautocomplete-select-option"
|
||||||
|
>
|
||||||
|
{suggestion.label}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{hasMore && (
|
||||||
|
<>
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
<div className={classes.progressContainer}>
|
||||||
|
<CircularProgress className={classes.progress} size={24} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<MenuItem
|
||||||
|
disabled={true}
|
||||||
|
component="div"
|
||||||
|
data-tc="singleautocomplete-select-no-options"
|
||||||
|
>
|
||||||
|
<FormattedMessage defaultMessage="No results found" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={classes.arrowContainer}>
|
||||||
|
<div
|
||||||
|
className={classNames(classes.arrowInnerContainer, {
|
||||||
|
// Needs to be explicitely compared to false because
|
||||||
|
// scrolledToBottom can be either true, false or undefined
|
||||||
|
[classes.hide]: scrolledToBottom !== false
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<SVG src={chevronDown} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SingleAutocompleteSelectFieldContent.displayName =
|
||||||
|
"SingleAutocompleteSelectFieldContent";
|
||||||
|
export default SingleAutocompleteSelectFieldContent;
|
|
@ -1,2 +1,3 @@
|
||||||
export { default } from "./SingleAutocompleteSelectField";
|
export { default } from "./SingleAutocompleteSelectField";
|
||||||
export * from "./SingleAutocompleteSelectField";
|
export * from "./SingleAutocompleteSelectField";
|
||||||
|
export * from "./SingleAutocompleteSelectFieldContent";
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const API_URI = process.env.API_URI || "/graphql/";
|
||||||
|
|
||||||
export const DEFAULT_INITIAL_SEARCH_DATA: SearchQueryVariables = {
|
export const DEFAULT_INITIAL_SEARCH_DATA: SearchQueryVariables = {
|
||||||
after: null,
|
after: null,
|
||||||
first: 5,
|
first: 20,
|
||||||
query: ""
|
query: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,26 +10,30 @@ export interface SearchQueryVariables {
|
||||||
query: string;
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BaseSearchProps<
|
||||||
|
TQuery,
|
||||||
|
TQueryVariables extends SearchQueryVariables
|
||||||
|
> {
|
||||||
|
children: (props: {
|
||||||
|
loadMore: () => void;
|
||||||
|
search: (query: string) => void;
|
||||||
|
result: TypedQueryResult<TQuery, TQueryVariables>;
|
||||||
|
}) => React.ReactElement<any>;
|
||||||
|
variables: TQueryVariables;
|
||||||
|
}
|
||||||
|
|
||||||
function BaseSearch<TQuery, TQueryVariables extends SearchQueryVariables>(
|
function BaseSearch<TQuery, TQueryVariables extends SearchQueryVariables>(
|
||||||
query: DocumentNode
|
query: DocumentNode,
|
||||||
|
loadMoreFn: (result: TypedQueryResult<TQuery, TQueryVariables>) => void
|
||||||
) {
|
) {
|
||||||
const Query = TypedQuery<TQuery, TQueryVariables>(query);
|
const Query = TypedQuery<TQuery, TQueryVariables>(query);
|
||||||
interface BaseSearchProps {
|
|
||||||
children: (props: {
|
|
||||||
search: (query: string) => void;
|
|
||||||
result: TypedQueryResult<TQuery, TQueryVariables>;
|
|
||||||
}) => React.ReactElement<any>;
|
|
||||||
variables: TQueryVariables;
|
|
||||||
}
|
|
||||||
interface BaseSearchState {
|
|
||||||
query: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseSearchComponent extends React.Component<
|
class BaseSearchComponent extends React.Component<
|
||||||
BaseSearchProps,
|
BaseSearchProps<TQuery, TQueryVariables>,
|
||||||
BaseSearchState
|
SearchQueryVariables
|
||||||
> {
|
> {
|
||||||
state: BaseSearchState = {
|
state: SearchQueryVariables = {
|
||||||
|
first: this.props.variables.first,
|
||||||
query: this.props.variables.query
|
query: this.props.variables.query
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,7 +58,13 @@ function BaseSearch<TQuery, TQueryVariables extends SearchQueryVariables>(
|
||||||
query: this.state.query
|
query: this.state.query
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{result => children({ search, result })}
|
{result =>
|
||||||
|
children({
|
||||||
|
loadMore: () => loadMoreFn(result),
|
||||||
|
result,
|
||||||
|
search
|
||||||
|
})
|
||||||
|
}
|
||||||
</Query>
|
</Query>
|
||||||
)}
|
)}
|
||||||
</Debounce>
|
</Debounce>
|
||||||
|
@ -63,4 +73,5 @@ function BaseSearch<TQuery, TQueryVariables extends SearchQueryVariables>(
|
||||||
}
|
}
|
||||||
return BaseSearchComponent;
|
return BaseSearchComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BaseSearch;
|
export default BaseSearch;
|
||||||
|
|
|
@ -1,24 +1,29 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import BaseSearch from "../BaseSearch";
|
import { pageInfoFragment } from "@saleor/queries";
|
||||||
|
import TopLevelSearch from "../TopLevelSearch";
|
||||||
import {
|
import {
|
||||||
SearchCategories,
|
SearchCategories,
|
||||||
SearchCategoriesVariables
|
SearchCategoriesVariables
|
||||||
} from "./types/SearchCategories";
|
} from "./types/SearchCategories";
|
||||||
|
|
||||||
export const searchCategories = gql`
|
export const searchCategories = gql`
|
||||||
|
${pageInfoFragment}
|
||||||
query SearchCategories($after: String, $first: Int!, $query: String!) {
|
query SearchCategories($after: String, $first: Int!, $query: String!) {
|
||||||
categories(after: $after, first: $first, query: $query) {
|
search: categories(after: $after, first: $first, query: $query) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageInfo {
|
||||||
|
...PageInfoFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default BaseSearch<SearchCategories, SearchCategoriesVariables>(
|
export default TopLevelSearch<SearchCategories, SearchCategoriesVariables>(
|
||||||
searchCategories
|
searchCategories
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,24 +6,33 @@
|
||||||
// GraphQL query operation: SearchCategories
|
// GraphQL query operation: SearchCategories
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface SearchCategories_categories_edges_node {
|
export interface SearchCategories_search_edges_node {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCategories_categories_edges {
|
export interface SearchCategories_search_edges {
|
||||||
__typename: "CategoryCountableEdge";
|
__typename: "CategoryCountableEdge";
|
||||||
node: SearchCategories_categories_edges_node;
|
node: SearchCategories_search_edges_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCategories_categories {
|
export interface SearchCategories_search_pageInfo {
|
||||||
|
__typename: "PageInfo";
|
||||||
|
endCursor: string | null;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
hasPreviousPage: boolean;
|
||||||
|
startCursor: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchCategories_search {
|
||||||
__typename: "CategoryCountableConnection";
|
__typename: "CategoryCountableConnection";
|
||||||
edges: SearchCategories_categories_edges[];
|
edges: SearchCategories_search_edges[];
|
||||||
|
pageInfo: SearchCategories_search_pageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCategories {
|
export interface SearchCategories {
|
||||||
categories: SearchCategories_categories | null;
|
search: SearchCategories_search | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCategoriesVariables {
|
export interface SearchCategoriesVariables {
|
||||||
|
|
|
@ -1,24 +1,29 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import BaseSearch from "../BaseSearch";
|
import { pageInfoFragment } from "@saleor/queries";
|
||||||
|
import TopLevelSearch from "../TopLevelSearch";
|
||||||
import {
|
import {
|
||||||
SearchCollections,
|
SearchCollections,
|
||||||
SearchCollectionsVariables
|
SearchCollectionsVariables
|
||||||
} from "./types/SearchCollections";
|
} from "./types/SearchCollections";
|
||||||
|
|
||||||
export const searchCollections = gql`
|
export const searchCollections = gql`
|
||||||
|
${pageInfoFragment}
|
||||||
query SearchCollections($after: String, $first: Int!, $query: String!) {
|
query SearchCollections($after: String, $first: Int!, $query: String!) {
|
||||||
collections(after: $after, first: $first, query: $query) {
|
search: collections(after: $after, first: $first, query: $query) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageInfo {
|
||||||
|
...PageInfoFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default BaseSearch<SearchCollections, SearchCollectionsVariables>(
|
export default TopLevelSearch<SearchCollections, SearchCollectionsVariables>(
|
||||||
searchCollections
|
searchCollections
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,24 +6,33 @@
|
||||||
// GraphQL query operation: SearchCollections
|
// GraphQL query operation: SearchCollections
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface SearchCollections_collections_edges_node {
|
export interface SearchCollections_search_edges_node {
|
||||||
__typename: "Collection";
|
__typename: "Collection";
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCollections_collections_edges {
|
export interface SearchCollections_search_edges {
|
||||||
__typename: "CollectionCountableEdge";
|
__typename: "CollectionCountableEdge";
|
||||||
node: SearchCollections_collections_edges_node;
|
node: SearchCollections_search_edges_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCollections_collections {
|
export interface SearchCollections_search_pageInfo {
|
||||||
|
__typename: "PageInfo";
|
||||||
|
endCursor: string | null;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
hasPreviousPage: boolean;
|
||||||
|
startCursor: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchCollections_search {
|
||||||
__typename: "CollectionCountableConnection";
|
__typename: "CollectionCountableConnection";
|
||||||
edges: SearchCollections_collections_edges[];
|
edges: SearchCollections_search_edges[];
|
||||||
|
pageInfo: SearchCollections_search_pageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCollections {
|
export interface SearchCollections {
|
||||||
collections: SearchCollections_collections | null;
|
search: SearchCollections_search | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCollectionsVariables {
|
export interface SearchCollectionsVariables {
|
||||||
|
|
|
@ -1,24 +1,29 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import BaseSearch from "../BaseSearch";
|
import { pageInfoFragment } from "@saleor/queries";
|
||||||
|
import TopLevelSearch from "../TopLevelSearch";
|
||||||
import {
|
import {
|
||||||
SearchCustomers,
|
SearchCustomers,
|
||||||
SearchCustomersVariables
|
SearchCustomersVariables
|
||||||
} from "./types/SearchCustomers";
|
} from "./types/SearchCustomers";
|
||||||
|
|
||||||
export const searchCustomers = gql`
|
export const searchCustomers = gql`
|
||||||
|
${pageInfoFragment}
|
||||||
query SearchCustomers($after: String, $first: Int!, $query: String!) {
|
query SearchCustomers($after: String, $first: Int!, $query: String!) {
|
||||||
customers(after: $after, first: $first, query: $query) {
|
search: customers(after: $after, first: $first, query: $query) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
email
|
email
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageInfo {
|
||||||
|
...PageInfoFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default BaseSearch<SearchCustomers, SearchCustomersVariables>(
|
export default TopLevelSearch<SearchCustomers, SearchCustomersVariables>(
|
||||||
searchCustomers
|
searchCustomers
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,24 +6,33 @@
|
||||||
// GraphQL query operation: SearchCustomers
|
// GraphQL query operation: SearchCustomers
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface SearchCustomers_customers_edges_node {
|
export interface SearchCustomers_search_edges_node {
|
||||||
__typename: "User";
|
__typename: "User";
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCustomers_customers_edges {
|
export interface SearchCustomers_search_edges {
|
||||||
__typename: "UserCountableEdge";
|
__typename: "UserCountableEdge";
|
||||||
node: SearchCustomers_customers_edges_node;
|
node: SearchCustomers_search_edges_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCustomers_customers {
|
export interface SearchCustomers_search_pageInfo {
|
||||||
|
__typename: "PageInfo";
|
||||||
|
endCursor: string | null;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
hasPreviousPage: boolean;
|
||||||
|
startCursor: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchCustomers_search {
|
||||||
__typename: "UserCountableConnection";
|
__typename: "UserCountableConnection";
|
||||||
edges: SearchCustomers_customers_edges[];
|
edges: SearchCustomers_search_edges[];
|
||||||
|
pageInfo: SearchCustomers_search_pageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCustomers {
|
export interface SearchCustomers {
|
||||||
customers: SearchCustomers_customers | null;
|
search: SearchCustomers_search | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCustomersVariables {
|
export interface SearchCustomersVariables {
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import BaseSearch from "../BaseSearch";
|
import { pageInfoFragment } from "@saleor/queries";
|
||||||
|
import TopLevelSearch from "../TopLevelSearch";
|
||||||
import { SearchPages, SearchPagesVariables } from "./types/SearchPages";
|
import { SearchPages, SearchPagesVariables } from "./types/SearchPages";
|
||||||
|
|
||||||
export const searchPages = gql`
|
export const searchPages = gql`
|
||||||
|
${pageInfoFragment}
|
||||||
query SearchPages($after: String, $first: Int!, $query: String!) {
|
query SearchPages($after: String, $first: Int!, $query: String!) {
|
||||||
pages(after: $after, first: $first, query: $query) {
|
search: pages(after: $after, first: $first, query: $query) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageInfo {
|
||||||
|
...PageInfoFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default BaseSearch<SearchPages, SearchPagesVariables>(searchPages);
|
export default TopLevelSearch<SearchPages, SearchPagesVariables>(searchPages);
|
||||||
|
|
|
@ -6,24 +6,33 @@
|
||||||
// GraphQL query operation: SearchPages
|
// GraphQL query operation: SearchPages
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface SearchPages_pages_edges_node {
|
export interface SearchPages_search_edges_node {
|
||||||
__typename: "Page";
|
__typename: "Page";
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchPages_pages_edges {
|
export interface SearchPages_search_edges {
|
||||||
__typename: "PageCountableEdge";
|
__typename: "PageCountableEdge";
|
||||||
node: SearchPages_pages_edges_node;
|
node: SearchPages_search_edges_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchPages_pages {
|
export interface SearchPages_search_pageInfo {
|
||||||
|
__typename: "PageInfo";
|
||||||
|
endCursor: string | null;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
hasPreviousPage: boolean;
|
||||||
|
startCursor: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchPages_search {
|
||||||
__typename: "PageCountableConnection";
|
__typename: "PageCountableConnection";
|
||||||
edges: SearchPages_pages_edges[];
|
edges: SearchPages_search_edges[];
|
||||||
|
pageInfo: SearchPages_search_pageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchPages {
|
export interface SearchPages {
|
||||||
pages: SearchPages_pages | null;
|
search: SearchPages_search | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchPagesVariables {
|
export interface SearchPagesVariables {
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import BaseSearch from "../BaseSearch";
|
import { pageInfoFragment } from "@saleor/queries";
|
||||||
|
import TopLevelSearch from "../TopLevelSearch";
|
||||||
import {
|
import {
|
||||||
SearchProductTypes,
|
SearchProductTypes,
|
||||||
SearchProductTypesVariables
|
SearchProductTypesVariables
|
||||||
} from "./types/SearchProductTypes";
|
} from "./types/SearchProductTypes";
|
||||||
|
|
||||||
export const searchProductTypes = gql`
|
export const searchProductTypes = gql`
|
||||||
|
${pageInfoFragment}
|
||||||
query SearchProductTypes($after: String, $first: Int!, $query: String!) {
|
query SearchProductTypes($after: String, $first: Int!, $query: String!) {
|
||||||
productTypes(after: $after, first: $first, filter: { search: $query }) {
|
search: productTypes(
|
||||||
|
after: $after
|
||||||
|
first: $first
|
||||||
|
filter: { search: $query }
|
||||||
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
|
@ -28,10 +34,13 @@ export const searchProductTypes = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageInfo {
|
||||||
|
...PageInfoFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default BaseSearch<SearchProductTypes, SearchProductTypesVariables>(
|
export default TopLevelSearch<SearchProductTypes, SearchProductTypesVariables>(
|
||||||
searchProductTypes
|
searchProductTypes
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,43 +8,52 @@ import { AttributeInputTypeEnum } from "./../../../types/globalTypes";
|
||||||
// GraphQL query operation: SearchProductTypes
|
// GraphQL query operation: SearchProductTypes
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface SearchProductTypes_productTypes_edges_node_productAttributes_values {
|
export interface SearchProductTypes_search_edges_node_productAttributes_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProductTypes_productTypes_edges_node_productAttributes {
|
export interface SearchProductTypes_search_edges_node_productAttributes {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
valueRequired: boolean;
|
valueRequired: boolean;
|
||||||
values: (SearchProductTypes_productTypes_edges_node_productAttributes_values | null)[] | null;
|
values: (SearchProductTypes_search_edges_node_productAttributes_values | null)[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProductTypes_productTypes_edges_node {
|
export interface SearchProductTypes_search_edges_node {
|
||||||
__typename: "ProductType";
|
__typename: "ProductType";
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
productAttributes: (SearchProductTypes_productTypes_edges_node_productAttributes | null)[] | null;
|
productAttributes: (SearchProductTypes_search_edges_node_productAttributes | null)[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProductTypes_productTypes_edges {
|
export interface SearchProductTypes_search_edges {
|
||||||
__typename: "ProductTypeCountableEdge";
|
__typename: "ProductTypeCountableEdge";
|
||||||
node: SearchProductTypes_productTypes_edges_node;
|
node: SearchProductTypes_search_edges_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProductTypes_productTypes {
|
export interface SearchProductTypes_search_pageInfo {
|
||||||
|
__typename: "PageInfo";
|
||||||
|
endCursor: string | null;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
hasPreviousPage: boolean;
|
||||||
|
startCursor: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchProductTypes_search {
|
||||||
__typename: "ProductTypeCountableConnection";
|
__typename: "ProductTypeCountableConnection";
|
||||||
edges: SearchProductTypes_productTypes_edges[];
|
edges: SearchProductTypes_search_edges[];
|
||||||
|
pageInfo: SearchProductTypes_search_pageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProductTypes {
|
export interface SearchProductTypes {
|
||||||
productTypes: SearchProductTypes_productTypes | null;
|
search: SearchProductTypes_search | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProductTypesVariables {
|
export interface SearchProductTypesVariables {
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import BaseSearch from "../BaseSearch";
|
import { pageInfoFragment } from "@saleor/queries";
|
||||||
|
import TopLevelSearch from "../TopLevelSearch";
|
||||||
import {
|
import {
|
||||||
SearchProducts,
|
SearchProducts,
|
||||||
SearchProductsVariables
|
SearchProductsVariables
|
||||||
} from "./types/SearchProducts";
|
} from "./types/SearchProducts";
|
||||||
|
|
||||||
export const searchProducts = gql`
|
export const searchProducts = gql`
|
||||||
|
${pageInfoFragment}
|
||||||
query SearchProducts($after: String, $first: Int!, $query: String!) {
|
query SearchProducts($after: String, $first: Int!, $query: String!) {
|
||||||
products(after: $after, first: $first, query: $query) {
|
search: products(after: $after, first: $first, query: $query) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
|
@ -18,10 +20,13 @@ export const searchProducts = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageInfo {
|
||||||
|
...PageInfoFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default BaseSearch<SearchProducts, SearchProductsVariables>(
|
export default TopLevelSearch<SearchProducts, SearchProductsVariables>(
|
||||||
searchProducts
|
searchProducts
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,30 +6,39 @@
|
||||||
// GraphQL query operation: SearchProducts
|
// GraphQL query operation: SearchProducts
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface SearchProducts_products_edges_node_thumbnail {
|
export interface SearchProducts_search_edges_node_thumbnail {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProducts_products_edges_node {
|
export interface SearchProducts_search_edges_node {
|
||||||
__typename: "Product";
|
__typename: "Product";
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
thumbnail: SearchProducts_products_edges_node_thumbnail | null;
|
thumbnail: SearchProducts_search_edges_node_thumbnail | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProducts_products_edges {
|
export interface SearchProducts_search_edges {
|
||||||
__typename: "ProductCountableEdge";
|
__typename: "ProductCountableEdge";
|
||||||
node: SearchProducts_products_edges_node;
|
node: SearchProducts_search_edges_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProducts_products {
|
export interface SearchProducts_search_pageInfo {
|
||||||
|
__typename: "PageInfo";
|
||||||
|
endCursor: string | null;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
hasPreviousPage: boolean;
|
||||||
|
startCursor: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchProducts_search {
|
||||||
__typename: "ProductCountableConnection";
|
__typename: "ProductCountableConnection";
|
||||||
edges: SearchProducts_products_edges[];
|
edges: SearchProducts_search_edges[];
|
||||||
|
pageInfo: SearchProducts_search_pageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProducts {
|
export interface SearchProducts {
|
||||||
products: SearchProducts_products | null;
|
search: SearchProducts_search | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchProductsVariables {
|
export interface SearchProductsVariables {
|
||||||
|
|
47
src/containers/TopLevelSearch.tsx
Normal file
47
src/containers/TopLevelSearch.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { DocumentNode } from "graphql";
|
||||||
|
|
||||||
|
import { PageInfoFragment } from "@saleor/types/PageInfoFragment";
|
||||||
|
import BaseSearch, { SearchQueryVariables } from "./BaseSearch";
|
||||||
|
|
||||||
|
export interface SearchQuery {
|
||||||
|
search: {
|
||||||
|
edges: Array<{
|
||||||
|
node: any;
|
||||||
|
}>;
|
||||||
|
pageInfo: PageInfoFragment;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function TopLevelSearch<
|
||||||
|
TQuery extends SearchQuery,
|
||||||
|
TQueryVariables extends SearchQueryVariables
|
||||||
|
>(query: DocumentNode) {
|
||||||
|
return BaseSearch<TQuery, TQueryVariables>(query, result => {
|
||||||
|
if (result.data.search.pageInfo.hasNextPage) {
|
||||||
|
result.loadMore(
|
||||||
|
(prev, next) => {
|
||||||
|
if (
|
||||||
|
prev.search.pageInfo.endCursor === next.search.pageInfo.endCursor
|
||||||
|
) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
search: {
|
||||||
|
...prev.search,
|
||||||
|
edges: [...prev.search.edges, ...next.search.edges],
|
||||||
|
pageInfo: next.search.pageInfo
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...result.variables,
|
||||||
|
after: result.data.search.pageInfo.endCursor
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TopLevelSearch;
|
|
@ -371,7 +371,7 @@ export const SaleDetails: React.StatelessComponent<SaleDetailsProps> = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
products={maybe(() =>
|
products={maybe(() =>
|
||||||
searchProductsOpts.data.products.edges
|
searchProductsOpts.data.search.edges
|
||||||
.map(edge => edge.node)
|
.map(edge => edge.node)
|
||||||
.filter(
|
.filter(
|
||||||
suggestedProduct => suggestedProduct.id
|
suggestedProduct => suggestedProduct.id
|
||||||
|
@ -389,7 +389,7 @@ export const SaleDetails: React.StatelessComponent<SaleDetailsProps> = ({
|
||||||
}) => (
|
}) => (
|
||||||
<AssignCategoriesDialog
|
<AssignCategoriesDialog
|
||||||
categories={maybe(() =>
|
categories={maybe(() =>
|
||||||
searchCategoriesOpts.data.categories.edges
|
searchCategoriesOpts.data.search.edges
|
||||||
.map(edge => edge.node)
|
.map(edge => edge.node)
|
||||||
.filter(
|
.filter(
|
||||||
suggestedCategory =>
|
suggestedCategory =>
|
||||||
|
@ -426,7 +426,7 @@ export const SaleDetails: React.StatelessComponent<SaleDetailsProps> = ({
|
||||||
}) => (
|
}) => (
|
||||||
<AssignCollectionDialog
|
<AssignCollectionDialog
|
||||||
collections={maybe(() =>
|
collections={maybe(() =>
|
||||||
searchCollectionsOpts.data.collections.edges
|
searchCollectionsOpts.data.search.edges
|
||||||
.map(edge => edge.node)
|
.map(edge => edge.node)
|
||||||
.filter(
|
.filter(
|
||||||
suggestedCategory =>
|
suggestedCategory =>
|
||||||
|
|
|
@ -429,7 +429,7 @@ export const VoucherDetails: React.StatelessComponent<VoucherDetailsProps> = ({
|
||||||
}) => (
|
}) => (
|
||||||
<AssignCategoriesDialog
|
<AssignCategoriesDialog
|
||||||
categories={maybe(() =>
|
categories={maybe(() =>
|
||||||
searchCategoriesOpts.data.categories.edges
|
searchCategoriesOpts.data.search.edges
|
||||||
.map(edge => edge.node)
|
.map(edge => edge.node)
|
||||||
.filter(
|
.filter(
|
||||||
suggestedCategory =>
|
suggestedCategory =>
|
||||||
|
@ -466,7 +466,7 @@ export const VoucherDetails: React.StatelessComponent<VoucherDetailsProps> = ({
|
||||||
}) => (
|
}) => (
|
||||||
<AssignCollectionDialog
|
<AssignCollectionDialog
|
||||||
collections={maybe(() =>
|
collections={maybe(() =>
|
||||||
searchCollectionsOpts.data.collections.edges
|
searchCollectionsOpts.data.search.edges
|
||||||
.map(edge => edge.node)
|
.map(edge => edge.node)
|
||||||
.filter(
|
.filter(
|
||||||
suggestedCategory =>
|
suggestedCategory =>
|
||||||
|
@ -544,7 +544,7 @@ export const VoucherDetails: React.StatelessComponent<VoucherDetailsProps> = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
products={maybe(() =>
|
products={maybe(() =>
|
||||||
searchProductsOpts.data.products.edges
|
searchProductsOpts.data.search.edges
|
||||||
.map(edge => edge.node)
|
.map(edge => edge.node)
|
||||||
.filter(
|
.filter(
|
||||||
suggestedProduct => suggestedProduct.id
|
suggestedProduct => suggestedProduct.id
|
||||||
|
|
248
src/fixtures.ts
248
src/fixtures.ts
|
@ -44,11 +44,249 @@ export const listActionsProps: ListActions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const countries = [
|
export const countries = [
|
||||||
{ code: "AF", label: "Afghanistan" },
|
{ name: "Afghanistan", code: "AF" },
|
||||||
{ code: "AX", label: "Åland Islands" },
|
{ name: "Åland Islands", code: "AX" },
|
||||||
{ code: "AL", label: "Albania" },
|
{ name: "Albania", code: "AL" },
|
||||||
{ code: "DZ", label: "Algeria" },
|
{ name: "Algeria", code: "DZ" },
|
||||||
{ code: "AS", label: "American Samoa" }
|
{ name: "American Samoa", code: "AS" },
|
||||||
|
{ name: "AndorrA", code: "AD" },
|
||||||
|
{ name: "Angola", code: "AO" },
|
||||||
|
{ name: "Anguilla", code: "AI" },
|
||||||
|
{ name: "Antarctica", code: "AQ" },
|
||||||
|
{ name: "Antigua and Barbuda", code: "AG" },
|
||||||
|
{ name: "Argentina", code: "AR" },
|
||||||
|
{ name: "Armenia", code: "AM" },
|
||||||
|
{ name: "Aruba", code: "AW" },
|
||||||
|
{ name: "Australia", code: "AU" },
|
||||||
|
{ name: "Austria", code: "AT" },
|
||||||
|
{ name: "Azerbaijan", code: "AZ" },
|
||||||
|
{ name: "Bahamas", code: "BS" },
|
||||||
|
{ name: "Bahrain", code: "BH" },
|
||||||
|
{ name: "Bangladesh", code: "BD" },
|
||||||
|
{ name: "Barbados", code: "BB" },
|
||||||
|
{ name: "Belarus", code: "BY" },
|
||||||
|
{ name: "Belgium", code: "BE" },
|
||||||
|
{ name: "Belize", code: "BZ" },
|
||||||
|
{ name: "Benin", code: "BJ" },
|
||||||
|
{ name: "Bermuda", code: "BM" },
|
||||||
|
{ name: "Bhutan", code: "BT" },
|
||||||
|
{ name: "Bolivia", code: "BO" },
|
||||||
|
{ name: "Bosnia and Herzegovina", code: "BA" },
|
||||||
|
{ name: "Botswana", code: "BW" },
|
||||||
|
{ name: "Bouvet Island", code: "BV" },
|
||||||
|
{ name: "Brazil", code: "BR" },
|
||||||
|
{ name: "British Indian Ocean Territory", code: "IO" },
|
||||||
|
{ name: "Brunei Darussalam", code: "BN" },
|
||||||
|
{ name: "Bulgaria", code: "BG" },
|
||||||
|
{ name: "Burkina Faso", code: "BF" },
|
||||||
|
{ name: "Burundi", code: "BI" },
|
||||||
|
{ name: "Cambodia", code: "KH" },
|
||||||
|
{ name: "Cameroon", code: "CM" },
|
||||||
|
{ name: "Canada", code: "CA" },
|
||||||
|
{ name: "Cape Verde", code: "CV" },
|
||||||
|
{ name: "Cayman Islands", code: "KY" },
|
||||||
|
{ name: "Central African Republic", code: "CF" },
|
||||||
|
{ name: "Chad", code: "TD" },
|
||||||
|
{ name: "Chile", code: "CL" },
|
||||||
|
{ name: "China", code: "CN" },
|
||||||
|
{ name: "Christmas Island", code: "CX" },
|
||||||
|
{ name: "Cocos (Keeling) Islands", code: "CC" },
|
||||||
|
{ name: "Colombia", code: "CO" },
|
||||||
|
{ name: "Comoros", code: "KM" },
|
||||||
|
{ name: "Congo", code: "CG" },
|
||||||
|
{ name: "Congo, The Democratic Republic of the", code: "CD" },
|
||||||
|
{ name: "Cook Islands", code: "CK" },
|
||||||
|
{ name: "Costa Rica", code: "CR" },
|
||||||
|
{ name: "Cote D'Ivoire", code: "CI" },
|
||||||
|
{ name: "Croatia", code: "HR" },
|
||||||
|
{ name: "Cuba", code: "CU" },
|
||||||
|
{ name: "Cyprus", code: "CY" },
|
||||||
|
{ name: "Czech Republic", code: "CZ" },
|
||||||
|
{ name: "Denmark", code: "DK" },
|
||||||
|
{ name: "Djibouti", code: "DJ" },
|
||||||
|
{ name: "Dominica", code: "DM" },
|
||||||
|
{ name: "Dominican Republic", code: "DO" },
|
||||||
|
{ name: "Ecuador", code: "EC" },
|
||||||
|
{ name: "Egypt", code: "EG" },
|
||||||
|
{ name: "El Salvador", code: "SV" },
|
||||||
|
{ name: "Equatorial Guinea", code: "GQ" },
|
||||||
|
{ name: "Eritrea", code: "ER" },
|
||||||
|
{ name: "Estonia", code: "EE" },
|
||||||
|
{ name: "Ethiopia", code: "ET" },
|
||||||
|
{ name: "Falkland Islands (Malvinas)", code: "FK" },
|
||||||
|
{ name: "Faroe Islands", code: "FO" },
|
||||||
|
{ name: "Fiji", code: "FJ" },
|
||||||
|
{ name: "Finland", code: "FI" },
|
||||||
|
{ name: "France", code: "FR" },
|
||||||
|
{ name: "French Guiana", code: "GF" },
|
||||||
|
{ name: "French Polynesia", code: "PF" },
|
||||||
|
{ name: "French Southern Territories", code: "TF" },
|
||||||
|
{ name: "Gabon", code: "GA" },
|
||||||
|
{ name: "Gambia", code: "GM" },
|
||||||
|
{ name: "Georgia", code: "GE" },
|
||||||
|
{ name: "Germany", code: "DE" },
|
||||||
|
{ name: "Ghana", code: "GH" },
|
||||||
|
{ name: "Gibraltar", code: "GI" },
|
||||||
|
{ name: "Greece", code: "GR" },
|
||||||
|
{ name: "Greenland", code: "GL" },
|
||||||
|
{ name: "Grenada", code: "GD" },
|
||||||
|
{ name: "Guadeloupe", code: "GP" },
|
||||||
|
{ name: "Guam", code: "GU" },
|
||||||
|
{ name: "Guatemala", code: "GT" },
|
||||||
|
{ name: "Guernsey", code: "GG" },
|
||||||
|
{ name: "Guinea", code: "GN" },
|
||||||
|
{ name: "Guinea-Bissau", code: "GW" },
|
||||||
|
{ name: "Guyana", code: "GY" },
|
||||||
|
{ name: "Haiti", code: "HT" },
|
||||||
|
{ name: "Heard Island and Mcdonald Islands", code: "HM" },
|
||||||
|
{ name: "Holy See (Vatican City State)", code: "VA" },
|
||||||
|
{ name: "Honduras", code: "HN" },
|
||||||
|
{ name: "Hong Kong", code: "HK" },
|
||||||
|
{ name: "Hungary", code: "HU" },
|
||||||
|
{ name: "Iceland", code: "IS" },
|
||||||
|
{ name: "India", code: "IN" },
|
||||||
|
{ name: "Indonesia", code: "ID" },
|
||||||
|
{ name: "Iran, Islamic Republic Of", code: "IR" },
|
||||||
|
{ name: "Iraq", code: "IQ" },
|
||||||
|
{ name: "Ireland", code: "IE" },
|
||||||
|
{ name: "Isle of Man", code: "IM" },
|
||||||
|
{ name: "Israel", code: "IL" },
|
||||||
|
{ name: "Italy", code: "IT" },
|
||||||
|
{ name: "Jamaica", code: "JM" },
|
||||||
|
{ name: "Japan", code: "JP" },
|
||||||
|
{ name: "Jersey", code: "JE" },
|
||||||
|
{ name: "Jordan", code: "JO" },
|
||||||
|
{ name: "Kazakhstan", code: "KZ" },
|
||||||
|
{ name: "Kenya", code: "KE" },
|
||||||
|
{ name: "Kiribati", code: "KI" },
|
||||||
|
{ name: "Korea, Democratic People'S Republic of", code: "KP" },
|
||||||
|
{ name: "Korea, Republic of", code: "KR" },
|
||||||
|
{ name: "Kuwait", code: "KW" },
|
||||||
|
{ name: "Kyrgyzstan", code: "KG" },
|
||||||
|
{ name: "Lao People'S Democratic Republic", code: "LA" },
|
||||||
|
{ name: "Latvia", code: "LV" },
|
||||||
|
{ name: "Lebanon", code: "LB" },
|
||||||
|
{ name: "Lesotho", code: "LS" },
|
||||||
|
{ name: "Liberia", code: "LR" },
|
||||||
|
{ name: "Libyan Arab Jamahiriya", code: "LY" },
|
||||||
|
{ name: "Liechtenstein", code: "LI" },
|
||||||
|
{ name: "Lithuania", code: "LT" },
|
||||||
|
{ name: "Luxembourg", code: "LU" },
|
||||||
|
{ name: "Macao", code: "MO" },
|
||||||
|
{ name: "Macedonia, The Former Yugoslav Republic of", code: "MK" },
|
||||||
|
{ name: "Madagascar", code: "MG" },
|
||||||
|
{ name: "Malawi", code: "MW" },
|
||||||
|
{ name: "Malaysia", code: "MY" },
|
||||||
|
{ name: "Maldives", code: "MV" },
|
||||||
|
{ name: "Mali", code: "ML" },
|
||||||
|
{ name: "Malta", code: "MT" },
|
||||||
|
{ name: "Marshall Islands", code: "MH" },
|
||||||
|
{ name: "Martinique", code: "MQ" },
|
||||||
|
{ name: "Mauritania", code: "MR" },
|
||||||
|
{ name: "Mauritius", code: "MU" },
|
||||||
|
{ name: "Mayotte", code: "YT" },
|
||||||
|
{ name: "Mexico", code: "MX" },
|
||||||
|
{ name: "Micronesia, Federated States of", code: "FM" },
|
||||||
|
{ name: "Moldova, Republic of", code: "MD" },
|
||||||
|
{ name: "Monaco", code: "MC" },
|
||||||
|
{ name: "Mongolia", code: "MN" },
|
||||||
|
{ name: "Montserrat", code: "MS" },
|
||||||
|
{ name: "Morocco", code: "MA" },
|
||||||
|
{ name: "Mozambique", code: "MZ" },
|
||||||
|
{ name: "Myanmar", code: "MM" },
|
||||||
|
{ name: "Namibia", code: "NA" },
|
||||||
|
{ name: "Nauru", code: "NR" },
|
||||||
|
{ name: "Nepal", code: "NP" },
|
||||||
|
{ name: "Netherlands", code: "NL" },
|
||||||
|
{ name: "Netherlands Antilles", code: "AN" },
|
||||||
|
{ name: "New Caledonia", code: "NC" },
|
||||||
|
{ name: "New Zealand", code: "NZ" },
|
||||||
|
{ name: "Nicaragua", code: "NI" },
|
||||||
|
{ name: "Niger", code: "NE" },
|
||||||
|
{ name: "Nigeria", code: "NG" },
|
||||||
|
{ name: "Niue", code: "NU" },
|
||||||
|
{ name: "Norfolk Island", code: "NF" },
|
||||||
|
{ name: "Northern Mariana Islands", code: "MP" },
|
||||||
|
{ name: "Norway", code: "NO" },
|
||||||
|
{ name: "Oman", code: "OM" },
|
||||||
|
{ name: "Pakistan", code: "PK" },
|
||||||
|
{ name: "Palau", code: "PW" },
|
||||||
|
{ name: "Palestinian Territory, Occupied", code: "PS" },
|
||||||
|
{ name: "Panama", code: "PA" },
|
||||||
|
{ name: "Papua New Guinea", code: "PG" },
|
||||||
|
{ name: "Paraguay", code: "PY" },
|
||||||
|
{ name: "Peru", code: "PE" },
|
||||||
|
{ name: "Philippines", code: "PH" },
|
||||||
|
{ name: "Pitcairn", code: "PN" },
|
||||||
|
{ name: "Poland", code: "PL" },
|
||||||
|
{ name: "Portugal", code: "PT" },
|
||||||
|
{ name: "Puerto Rico", code: "PR" },
|
||||||
|
{ name: "Qatar", code: "QA" },
|
||||||
|
{ name: "Reunion", code: "RE" },
|
||||||
|
{ name: "Romania", code: "RO" },
|
||||||
|
{ name: "Russian Federation", code: "RU" },
|
||||||
|
{ name: "RWANDA", code: "RW" },
|
||||||
|
{ name: "Saint Helena", code: "SH" },
|
||||||
|
{ name: "Saint Kitts and Nevis", code: "KN" },
|
||||||
|
{ name: "Saint Lucia", code: "LC" },
|
||||||
|
{ name: "Saint Pierre and Miquelon", code: "PM" },
|
||||||
|
{ name: "Saint Vincent and the Grenadines", code: "VC" },
|
||||||
|
{ name: "Samoa", code: "WS" },
|
||||||
|
{ name: "San Marino", code: "SM" },
|
||||||
|
{ name: "Sao Tome and Principe", code: "ST" },
|
||||||
|
{ name: "Saudi Arabia", code: "SA" },
|
||||||
|
{ name: "Senegal", code: "SN" },
|
||||||
|
{ name: "Serbia and Montenegro", code: "CS" },
|
||||||
|
{ name: "Seychelles", code: "SC" },
|
||||||
|
{ name: "Sierra Leone", code: "SL" },
|
||||||
|
{ name: "Singapore", code: "SG" },
|
||||||
|
{ name: "Slovakia", code: "SK" },
|
||||||
|
{ name: "Slovenia", code: "SI" },
|
||||||
|
{ name: "Solomon Islands", code: "SB" },
|
||||||
|
{ name: "Somalia", code: "SO" },
|
||||||
|
{ name: "South Africa", code: "ZA" },
|
||||||
|
{ name: "South Georgia and the South Sandwich Islands", code: "GS" },
|
||||||
|
{ name: "Spain", code: "ES" },
|
||||||
|
{ name: "Sri Lanka", code: "LK" },
|
||||||
|
{ name: "Sudan", code: "SD" },
|
||||||
|
{ name: "Suriname", code: "SR" },
|
||||||
|
{ name: "Svalbard and Jan Mayen", code: "SJ" },
|
||||||
|
{ name: "Swaziland", code: "SZ" },
|
||||||
|
{ name: "Sweden", code: "SE" },
|
||||||
|
{ name: "Switzerland", code: "CH" },
|
||||||
|
{ name: "Syrian Arab Republic", code: "SY" },
|
||||||
|
{ name: "Taiwan, Province of China", code: "TW" },
|
||||||
|
{ name: "Tajikistan", code: "TJ" },
|
||||||
|
{ name: "Tanzania, United Republic of", code: "TZ" },
|
||||||
|
{ name: "Thailand", code: "TH" },
|
||||||
|
{ name: "Timor-Leste", code: "TL" },
|
||||||
|
{ name: "Togo", code: "TG" },
|
||||||
|
{ name: "Tokelau", code: "TK" },
|
||||||
|
{ name: "Tonga", code: "TO" },
|
||||||
|
{ name: "Trinidad and Tobago", code: "TT" },
|
||||||
|
{ name: "Tunisia", code: "TN" },
|
||||||
|
{ name: "Turkey", code: "TR" },
|
||||||
|
{ name: "Turkmenistan", code: "TM" },
|
||||||
|
{ name: "Turks and Caicos Islands", code: "TC" },
|
||||||
|
{ name: "Tuvalu", code: "TV" },
|
||||||
|
{ name: "Uganda", code: "UG" },
|
||||||
|
{ name: "Ukraine", code: "UA" },
|
||||||
|
{ name: "United Arab Emirates", code: "AE" },
|
||||||
|
{ name: "United Kingdom", code: "GB" },
|
||||||
|
{ name: "United States", code: "US" },
|
||||||
|
{ name: "United States Minor Outlying Islands", code: "UM" },
|
||||||
|
{ name: "Uruguay", code: "UY" },
|
||||||
|
{ name: "Uzbekistan", code: "UZ" },
|
||||||
|
{ name: "Vanuatu", code: "VU" },
|
||||||
|
{ name: "Venezuela", code: "VE" },
|
||||||
|
{ name: "Viet Nam", code: "VN" },
|
||||||
|
{ name: "Virgin Islands, British", code: "VG" },
|
||||||
|
{ name: "Virgin Islands, U.S.", code: "VI" },
|
||||||
|
{ name: "Wallis and Futuna", code: "WF" },
|
||||||
|
{ name: "Western Sahara", code: "EH" },
|
||||||
|
{ name: "Yemen", code: "YE" },
|
||||||
|
{ name: "Zambia", code: "ZM" },
|
||||||
|
{ name: "Zimbabwe", code: "ZW" }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const tabPageProps: TabPageProps = {
|
export const tabPageProps: TabPageProps = {
|
||||||
|
|
|
@ -1,20 +1,30 @@
|
||||||
import throttle from "lodash-es/throttle";
|
import throttle from "lodash-es/throttle";
|
||||||
import { MutableRefObject, useEffect, useState } from "react";
|
import { MutableRefObject, useEffect, useState } from "react";
|
||||||
|
|
||||||
function getPosition(anchor?: HTMLElement) {
|
export type Position = Record<"x" | "y", number>;
|
||||||
|
|
||||||
|
function getPosition(anchor?: HTMLElement): Position {
|
||||||
if (!!anchor) {
|
if (!!anchor) {
|
||||||
return {
|
return {
|
||||||
x: anchor.scrollLeft,
|
x: anchor.scrollLeft,
|
||||||
y: anchor.scrollTop
|
y: anchor.scrollTop
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return undefined;
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function useElementScroll(anchor: MutableRefObject<HTMLElement>) {
|
export function isScrolledToBottom(
|
||||||
|
anchor: MutableRefObject<HTMLElement>,
|
||||||
|
position: Position,
|
||||||
|
offset: number = 0
|
||||||
|
) {
|
||||||
|
return !!anchor.current && position
|
||||||
|
? position.y + anchor.current.clientHeight + offset >=
|
||||||
|
anchor.current.scrollHeight
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useElementScroll(anchor: MutableRefObject<HTMLElement>): Position {
|
||||||
const [scroll, setScroll] = useState(getPosition(anchor.current));
|
const [scroll, setScroll] = useState(getPosition(anchor.current));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -29,6 +39,10 @@ function useElementScroll(anchor: MutableRefObject<HTMLElement>) {
|
||||||
}
|
}
|
||||||
}, [anchor.current]);
|
}, [anchor.current]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => setScroll(getPosition(anchor.current)), 100);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return scroll;
|
return scroll;
|
||||||
}
|
}
|
||||||
export default useElementScroll;
|
export default useElementScroll;
|
||||||
|
|
|
@ -14,9 +14,9 @@ import ConfirmButton, {
|
||||||
ConfirmButtonTransitionState
|
ConfirmButtonTransitionState
|
||||||
} from "@saleor/components/ConfirmButton";
|
} from "@saleor/components/ConfirmButton";
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import { SearchCategories_categories_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
|
import { SearchCategories_search_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
|
||||||
import { SearchCollections_collections_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
|
import { SearchCollections_search_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
|
||||||
import { SearchPages_pages_edges_node } from "@saleor/containers/SearchPages/types/SearchPages";
|
import { SearchPages_search_edges_node } from "@saleor/containers/SearchPages/types/SearchPages";
|
||||||
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
|
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
|
||||||
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
|
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
|
@ -43,9 +43,9 @@ export interface MenuItemDialogProps {
|
||||||
initialDisplayValue?: string;
|
initialDisplayValue?: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
collections: SearchCollections_collections_edges_node[];
|
collections: SearchCollections_search_edges_node[];
|
||||||
categories: SearchCategories_categories_edges_node[];
|
categories: SearchCategories_search_edges_node[];
|
||||||
pages: SearchPages_pages_edges_node[];
|
pages: SearchPages_search_edges_node[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (data: MenuItemDialogFormData) => void;
|
onSubmit: (data: MenuItemDialogFormData) => void;
|
||||||
onQueryChange: (query: string) => void;
|
onQueryChange: (query: string) => void;
|
||||||
|
|
|
@ -111,7 +111,7 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
|
||||||
|
|
||||||
const categories = maybe(
|
const categories = maybe(
|
||||||
() =>
|
() =>
|
||||||
categorySearch.result.data.categories.edges.map(
|
categorySearch.result.data.search.edges.map(
|
||||||
edge => edge.node
|
edge => edge.node
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
|
@ -119,7 +119,7 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
|
||||||
|
|
||||||
const collections = maybe(
|
const collections = maybe(
|
||||||
() =>
|
() =>
|
||||||
collectionSearch.result.data.collections.edges.map(
|
collectionSearch.result.data.search.edges.map(
|
||||||
edge => edge.node
|
edge => edge.node
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
|
@ -127,7 +127,7 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
|
||||||
|
|
||||||
const pages = maybe(
|
const pages = maybe(
|
||||||
() =>
|
() =>
|
||||||
pageSearch.result.data.pages.edges.map(
|
pageSearch.result.data.search.edges.map(
|
||||||
edge => edge.node
|
edge => edge.node
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
|
|
|
@ -20,8 +20,9 @@ import SingleAutocompleteSelectField from "@saleor/components/SingleAutocomplete
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
import { SearchCustomers_customers_edges_node } from "../../../containers/SearchCustomers/types/SearchCustomers";
|
import { SearchCustomers_search_edges_node } from "../../../containers/SearchCustomers/types/SearchCustomers";
|
||||||
import { customerUrl } from "../../../customers/urls";
|
import { customerUrl } from "../../../customers/urls";
|
||||||
import { createHref, maybe } from "../../../misc";
|
import { createHref, maybe } from "../../../misc";
|
||||||
import { OrderDetails_order } from "../../types/OrderDetails";
|
import { OrderDetails_order } from "../../types/OrderDetails";
|
||||||
|
@ -48,9 +49,9 @@ const styles = (theme: Theme) =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface OrderCustomerProps extends WithStyles<typeof styles> {
|
export interface OrderCustomerProps extends Partial<FetchMoreProps> {
|
||||||
order: OrderDetails_order;
|
order: OrderDetails_order;
|
||||||
users?: SearchCustomers_customers_edges_node[];
|
users?: SearchCustomers_search_edges_node[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
canEditAddresses: boolean;
|
canEditAddresses: boolean;
|
||||||
canEditCustomer: boolean;
|
canEditCustomer: boolean;
|
||||||
|
@ -67,14 +68,16 @@ const OrderCustomer = withStyles(styles, { name: "OrderCustomer" })(
|
||||||
canEditAddresses,
|
canEditAddresses,
|
||||||
canEditCustomer,
|
canEditCustomer,
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
|
hasMore: hasMoreUsers,
|
||||||
loading,
|
loading,
|
||||||
order,
|
order,
|
||||||
users,
|
users,
|
||||||
onCustomerEdit,
|
onCustomerEdit,
|
||||||
onBillingAddressEdit,
|
onBillingAddressEdit,
|
||||||
|
onFetchMore: onFetchMoreUsers,
|
||||||
onProfileView,
|
onProfileView,
|
||||||
onShippingAddressEdit
|
onShippingAddressEdit
|
||||||
}: OrderCustomerProps) => {
|
}: OrderCustomerProps & WithStyles<typeof styles>) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const user = maybe(() => order.user);
|
const user = maybe(() => order.user);
|
||||||
|
@ -138,11 +141,13 @@ const OrderCustomer = withStyles(styles, { name: "OrderCustomer" })(
|
||||||
choices={userChoices}
|
choices={userChoices}
|
||||||
displayValue={userDisplayName}
|
displayValue={userDisplayName}
|
||||||
fetchChoices={fetchUsers}
|
fetchChoices={fetchUsers}
|
||||||
|
hasMore={hasMoreUsers}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
placeholder={intl.formatMessage({
|
placeholder={intl.formatMessage({
|
||||||
defaultMessage: "Search Customers"
|
defaultMessage: "Search Customers"
|
||||||
})}
|
})}
|
||||||
onChange={handleUserChange}
|
onChange={handleUserChange}
|
||||||
|
onFetchMore={onFetchMoreUsers}
|
||||||
name="query"
|
name="query"
|
||||||
value={data.query}
|
value={data.query}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import Dialog from "@material-ui/core/Dialog";
|
|
||||||
import DialogActions from "@material-ui/core/DialogActions";
|
|
||||||
import DialogContent from "@material-ui/core/DialogContent";
|
|
||||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
|
||||||
import {
|
|
||||||
createStyles,
|
|
||||||
Theme,
|
|
||||||
withStyles,
|
|
||||||
WithStyles
|
|
||||||
} from "@material-ui/core/styles";
|
|
||||||
import React from "react";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
|
|
||||||
import ConfirmButton, {
|
|
||||||
ConfirmButtonTransitionState
|
|
||||||
} from "@saleor/components/ConfirmButton";
|
|
||||||
import { SingleAutocompleteSelectField } from "@saleor/components/SingleAutocompleteSelectField";
|
|
||||||
import { buttonMessages } from "@saleor/intl";
|
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
dialog: {
|
|
||||||
overflowY: "visible"
|
|
||||||
},
|
|
||||||
root: {
|
|
||||||
overflowY: "visible",
|
|
||||||
width: theme.breakpoints.values.sm
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
flex: 1,
|
|
||||||
marginRight: theme.spacing.unit * 2
|
|
||||||
},
|
|
||||||
textRight: {
|
|
||||||
textAlign: "right"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface OrderCustomerEditDialogProps extends WithStyles<typeof styles> {
|
|
||||||
confirmButtonState: ConfirmButtonTransitionState;
|
|
||||||
open: boolean;
|
|
||||||
user: string;
|
|
||||||
userDisplayValue: string;
|
|
||||||
users?: Array<{
|
|
||||||
id: string;
|
|
||||||
email: string;
|
|
||||||
}>;
|
|
||||||
loading?: boolean;
|
|
||||||
fetchUsers(value: string);
|
|
||||||
onChange(event: React.ChangeEvent<any>);
|
|
||||||
onClose?();
|
|
||||||
onConfirm?(event: React.FormEvent<any>);
|
|
||||||
}
|
|
||||||
|
|
||||||
const OrderCustomerEditDialog = withStyles(styles, {
|
|
||||||
name: "OrderCustomerEditDialog"
|
|
||||||
})(
|
|
||||||
({
|
|
||||||
classes,
|
|
||||||
confirmButtonState,
|
|
||||||
open,
|
|
||||||
loading,
|
|
||||||
user,
|
|
||||||
userDisplayValue,
|
|
||||||
users,
|
|
||||||
fetchUsers,
|
|
||||||
onChange,
|
|
||||||
onClose,
|
|
||||||
onConfirm
|
|
||||||
}: OrderCustomerEditDialogProps) => {
|
|
||||||
const choices =
|
|
||||||
!loading && users
|
|
||||||
? users.map(v => ({
|
|
||||||
label: v.email,
|
|
||||||
value: v.id
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
return (
|
|
||||||
<Dialog onClose={onClose} open={open} classes={{ paper: classes.dialog }}>
|
|
||||||
<DialogTitle>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Edit Customer Details"
|
|
||||||
description="dialog header"
|
|
||||||
/>
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent className={classes.root}>
|
|
||||||
<SingleAutocompleteSelectField
|
|
||||||
choices={choices}
|
|
||||||
allowCustomValues
|
|
||||||
loading={loading}
|
|
||||||
displayValue={userDisplayValue}
|
|
||||||
name="user"
|
|
||||||
value={user}
|
|
||||||
fetchChoices={fetchUsers}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onClose}>
|
|
||||||
<FormattedMessage {...buttonMessages.cancel} />
|
|
||||||
</Button>
|
|
||||||
<ConfirmButton
|
|
||||||
transitionState={confirmButtonState}
|
|
||||||
color="primary"
|
|
||||||
variant="contained"
|
|
||||||
onClick={onConfirm}
|
|
||||||
>
|
|
||||||
<FormattedMessage {...buttonMessages.confirm} />
|
|
||||||
</ConfirmButton>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
OrderCustomerEditDialog.displayName = "OrderCustomerEditDialog";
|
|
||||||
export default OrderCustomerEditDialog;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./OrderCustomerEditDialog";
|
|
||||||
export * from "./OrderCustomerEditDialog";
|
|
|
@ -18,7 +18,8 @@ import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { SearchCustomers_customers_edges_node } from "../../../containers/SearchCustomers/types/SearchCustomers";
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
|
import { SearchCustomers_search_edges_node } from "../../../containers/SearchCustomers/types/SearchCustomers";
|
||||||
import { maybe } from "../../../misc";
|
import { maybe } from "../../../misc";
|
||||||
import { DraftOrderInput } from "../../../types/globalTypes";
|
import { DraftOrderInput } from "../../../types/globalTypes";
|
||||||
import { OrderDetails_order } from "../../types/OrderDetails";
|
import { OrderDetails_order } from "../../types/OrderDetails";
|
||||||
|
@ -38,10 +39,10 @@ const styles = (theme: Theme) =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface OrderDraftPageProps extends WithStyles<typeof styles> {
|
export interface OrderDraftPageProps extends FetchMoreProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
order: OrderDetails_order;
|
order: OrderDetails_order;
|
||||||
users: SearchCustomers_customers_edges_node[];
|
users: SearchCustomers_search_edges_node[];
|
||||||
usersLoading: boolean;
|
usersLoading: boolean;
|
||||||
countries: Array<{
|
countries: Array<{
|
||||||
code: string;
|
code: string;
|
||||||
|
@ -72,12 +73,14 @@ const OrderDraftPage = withStyles(styles, { name: "OrderDraftPage" })(
|
||||||
classes,
|
classes,
|
||||||
disabled,
|
disabled,
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
|
hasMore,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
onBack,
|
onBack,
|
||||||
onBillingAddressEdit,
|
onBillingAddressEdit,
|
||||||
onCustomerEdit,
|
onCustomerEdit,
|
||||||
onDraftFinalize,
|
onDraftFinalize,
|
||||||
onDraftRemove,
|
onDraftRemove,
|
||||||
|
onFetchMore,
|
||||||
onNoteAdd,
|
onNoteAdd,
|
||||||
onOrderLineAdd,
|
onOrderLineAdd,
|
||||||
onOrderLineChange,
|
onOrderLineChange,
|
||||||
|
@ -88,7 +91,7 @@ const OrderDraftPage = withStyles(styles, { name: "OrderDraftPage" })(
|
||||||
order,
|
order,
|
||||||
users,
|
users,
|
||||||
usersLoading
|
usersLoading
|
||||||
}: OrderDraftPageProps) => {
|
}: OrderDraftPageProps & WithStyles<typeof styles>) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -139,14 +142,16 @@ const OrderDraftPage = withStyles(styles, { name: "OrderDraftPage" })(
|
||||||
<OrderCustomer
|
<OrderCustomer
|
||||||
canEditAddresses={true}
|
canEditAddresses={true}
|
||||||
canEditCustomer={true}
|
canEditCustomer={true}
|
||||||
|
fetchUsers={fetchUsers}
|
||||||
|
hasMore={hasMore}
|
||||||
|
loading={usersLoading}
|
||||||
order={order}
|
order={order}
|
||||||
users={users}
|
users={users}
|
||||||
loading={usersLoading}
|
|
||||||
fetchUsers={fetchUsers}
|
|
||||||
onBillingAddressEdit={onBillingAddressEdit}
|
onBillingAddressEdit={onBillingAddressEdit}
|
||||||
onCustomerEdit={onCustomerEdit}
|
onCustomerEdit={onCustomerEdit}
|
||||||
onShippingAddressEdit={onShippingAddressEdit}
|
onFetchMore={onFetchMore}
|
||||||
onProfileView={onProfileView}
|
onProfileView={onProfileView}
|
||||||
|
onShippingAddressEdit={onShippingAddressEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -30,8 +30,8 @@ import { buttonMessages } from "@saleor/intl";
|
||||||
import { maybe, renderCollection } from "@saleor/misc";
|
import { maybe, renderCollection } from "@saleor/misc";
|
||||||
import { FetchMoreProps } from "@saleor/types";
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import {
|
import {
|
||||||
SearchOrderVariant_products_edges_node,
|
SearchOrderVariant_search_edges_node,
|
||||||
SearchOrderVariant_products_edges_node_variants
|
SearchOrderVariant_search_edges_node_variants
|
||||||
} from "../../types/SearchOrderVariant";
|
} from "../../types/SearchOrderVariant";
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
|
@ -79,21 +79,21 @@ const styles = (theme: Theme) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
type SetVariantsAction = (
|
type SetVariantsAction = (
|
||||||
data: SearchOrderVariant_products_edges_node_variants[]
|
data: SearchOrderVariant_search_edges_node_variants[]
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
interface OrderProductAddDialogProps extends FetchMoreProps {
|
interface OrderProductAddDialogProps extends FetchMoreProps {
|
||||||
confirmButtonState: ConfirmButtonTransitionState;
|
confirmButtonState: ConfirmButtonTransitionState;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
products: SearchOrderVariant_products_edges_node[];
|
products: SearchOrderVariant_search_edges_node[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onFetch: (query: string) => void;
|
onFetch: (query: string) => void;
|
||||||
onSubmit: (data: SearchOrderVariant_products_edges_node_variants[]) => void;
|
onSubmit: (data: SearchOrderVariant_search_edges_node_variants[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasAllVariantsSelected(
|
function hasAllVariantsSelected(
|
||||||
productVariants: SearchOrderVariant_products_edges_node_variants[],
|
productVariants: SearchOrderVariant_search_edges_node_variants[],
|
||||||
selectedVariantsToProductsMap: SearchOrderVariant_products_edges_node_variants[]
|
selectedVariantsToProductsMap: SearchOrderVariant_search_edges_node_variants[]
|
||||||
): boolean {
|
): boolean {
|
||||||
return productVariants.reduce(
|
return productVariants.reduce(
|
||||||
(acc, productVariant) =>
|
(acc, productVariant) =>
|
||||||
|
@ -106,8 +106,8 @@ function hasAllVariantsSelected(
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVariantSelected(
|
function isVariantSelected(
|
||||||
variant: SearchOrderVariant_products_edges_node_variants,
|
variant: SearchOrderVariant_search_edges_node_variants,
|
||||||
selectedVariantsToProductsMap: SearchOrderVariant_products_edges_node_variants[]
|
selectedVariantsToProductsMap: SearchOrderVariant_search_edges_node_variants[]
|
||||||
): boolean {
|
): boolean {
|
||||||
return !!selectedVariantsToProductsMap.find(
|
return !!selectedVariantsToProductsMap.find(
|
||||||
selectedVariant => selectedVariant.id === variant.id
|
selectedVariant => selectedVariant.id === variant.id
|
||||||
|
@ -115,10 +115,10 @@ function isVariantSelected(
|
||||||
}
|
}
|
||||||
|
|
||||||
const onProductAdd = (
|
const onProductAdd = (
|
||||||
product: SearchOrderVariant_products_edges_node,
|
product: SearchOrderVariant_search_edges_node,
|
||||||
productIndex: number,
|
productIndex: number,
|
||||||
productsWithAllVariantsSelected: boolean[],
|
productsWithAllVariantsSelected: boolean[],
|
||||||
variants: SearchOrderVariant_products_edges_node_variants[],
|
variants: SearchOrderVariant_search_edges_node_variants[],
|
||||||
setVariants: SetVariantsAction
|
setVariants: SetVariantsAction
|
||||||
) =>
|
) =>
|
||||||
productsWithAllVariantsSelected[productIndex]
|
productsWithAllVariantsSelected[productIndex]
|
||||||
|
@ -141,10 +141,10 @@ const onProductAdd = (
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onVariantAdd = (
|
const onVariantAdd = (
|
||||||
variant: SearchOrderVariant_products_edges_node_variants,
|
variant: SearchOrderVariant_search_edges_node_variants,
|
||||||
variantIndex: number,
|
variantIndex: number,
|
||||||
productIndex: number,
|
productIndex: number,
|
||||||
variants: SearchOrderVariant_products_edges_node_variants[],
|
variants: SearchOrderVariant_search_edges_node_variants[],
|
||||||
selectedVariantsToProductsMap: boolean[][],
|
selectedVariantsToProductsMap: boolean[][],
|
||||||
setVariants: SetVariantsAction
|
setVariants: SetVariantsAction
|
||||||
) =>
|
) =>
|
||||||
|
@ -172,7 +172,7 @@ const OrderProductAddDialog = withStyles(styles, {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||||
const [variants, setVariants] = React.useState<
|
const [variants, setVariants] = React.useState<
|
||||||
SearchOrderVariant_products_edges_node_variants[]
|
SearchOrderVariant_search_edges_node_variants[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const selectedVariantsToProductsMap = products
|
const selectedVariantsToProductsMap = products
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { MessageDescriptor } from "react-intl";
|
import { MessageDescriptor } from "react-intl";
|
||||||
import { SearchCustomers_customers_edges_node } from "../containers/SearchCustomers/types/SearchCustomers";
|
import { SearchCustomers_search_edges_node } from "../containers/SearchCustomers/types/SearchCustomers";
|
||||||
import { transformOrderStatus, transformPaymentStatus } from "../misc";
|
import { transformOrderStatus, transformPaymentStatus } from "../misc";
|
||||||
import {
|
import {
|
||||||
FulfillmentStatus,
|
FulfillmentStatus,
|
||||||
|
@ -11,7 +11,7 @@ import {
|
||||||
import { OrderDetails_order } from "./types/OrderDetails";
|
import { OrderDetails_order } from "./types/OrderDetails";
|
||||||
import { OrderList_orders_edges_node } from "./types/OrderList";
|
import { OrderList_orders_edges_node } from "./types/OrderList";
|
||||||
|
|
||||||
export const clients: SearchCustomers_customers_edges_node[] = [
|
export const clients: SearchCustomers_search_edges_node[] = [
|
||||||
{
|
{
|
||||||
__typename: "User" as "User",
|
__typename: "User" as "User",
|
||||||
email: "test.client1@example.com",
|
email: "test.client1@example.com",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import BaseSearch from "../containers/BaseSearch";
|
import TopLevelSearch from "../containers/TopLevelSearch";
|
||||||
import { TypedQuery } from "../queries";
|
import { TypedQuery } from "../queries";
|
||||||
import { OrderDetails, OrderDetailsVariables } from "./types/OrderDetails";
|
import { OrderDetails, OrderDetailsVariables } from "./types/OrderDetails";
|
||||||
import {
|
import {
|
||||||
|
@ -286,7 +286,7 @@ export const TypedOrderDetailsQuery = TypedQuery<
|
||||||
|
|
||||||
export const searchOrderVariant = gql`
|
export const searchOrderVariant = gql`
|
||||||
query SearchOrderVariant($first: Int!, $query: String!, $after: String) {
|
query SearchOrderVariant($first: Int!, $query: String!, $after: String) {
|
||||||
products(query: $query, first: $first, after: $after) {
|
search: products(query: $query, first: $first, after: $after) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
|
@ -314,7 +314,7 @@ export const searchOrderVariant = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const SearchOrderVariant = BaseSearch<
|
export const SearchOrderVariant = TopLevelSearch<
|
||||||
SearchOrderVariantType,
|
SearchOrderVariantType,
|
||||||
SearchOrderVariantVariables
|
SearchOrderVariantVariables
|
||||||
>(searchOrderVariant);
|
>(searchOrderVariant);
|
||||||
|
|
|
@ -6,39 +6,39 @@
|
||||||
// GraphQL query operation: SearchOrderVariant
|
// GraphQL query operation: SearchOrderVariant
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface SearchOrderVariant_products_edges_node_thumbnail {
|
export interface SearchOrderVariant_search_edges_node_thumbnail {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOrderVariant_products_edges_node_variants_price {
|
export interface SearchOrderVariant_search_edges_node_variants_price {
|
||||||
__typename: "Money";
|
__typename: "Money";
|
||||||
amount: number;
|
amount: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOrderVariant_products_edges_node_variants {
|
export interface SearchOrderVariant_search_edges_node_variants {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
sku: string;
|
sku: string;
|
||||||
price: SearchOrderVariant_products_edges_node_variants_price | null;
|
price: SearchOrderVariant_search_edges_node_variants_price | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOrderVariant_products_edges_node {
|
export interface SearchOrderVariant_search_edges_node {
|
||||||
__typename: "Product";
|
__typename: "Product";
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
thumbnail: SearchOrderVariant_products_edges_node_thumbnail | null;
|
thumbnail: SearchOrderVariant_search_edges_node_thumbnail | null;
|
||||||
variants: (SearchOrderVariant_products_edges_node_variants | null)[] | null;
|
variants: (SearchOrderVariant_search_edges_node_variants | null)[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOrderVariant_products_edges {
|
export interface SearchOrderVariant_search_edges {
|
||||||
__typename: "ProductCountableEdge";
|
__typename: "ProductCountableEdge";
|
||||||
node: SearchOrderVariant_products_edges_node;
|
node: SearchOrderVariant_search_edges_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOrderVariant_products_pageInfo {
|
export interface SearchOrderVariant_search_pageInfo {
|
||||||
__typename: "PageInfo";
|
__typename: "PageInfo";
|
||||||
endCursor: string | null;
|
endCursor: string | null;
|
||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
|
@ -46,14 +46,14 @@ export interface SearchOrderVariant_products_pageInfo {
|
||||||
startCursor: string | null;
|
startCursor: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOrderVariant_products {
|
export interface SearchOrderVariant_search {
|
||||||
__typename: "ProductCountableConnection";
|
__typename: "ProductCountableConnection";
|
||||||
edges: SearchOrderVariant_products_edges[];
|
edges: SearchOrderVariant_search_edges[];
|
||||||
pageInfo: SearchOrderVariant_products_pageInfo;
|
pageInfo: SearchOrderVariant_search_pageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOrderVariant {
|
export interface SearchOrderVariant {
|
||||||
products: SearchOrderVariant_products | null;
|
search: SearchOrderVariant_search | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOrderVariantVariables {
|
export interface SearchOrderVariantVariables {
|
||||||
|
|
|
@ -93,7 +93,11 @@ export const OrderDetails: React.StatelessComponent<OrderDetailsProps> = ({
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<SearchCustomers variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
<SearchCustomers variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
||||||
{({ search: searchUsers, result: users }) => (
|
{({
|
||||||
|
loadMore: loadMoreCustomers,
|
||||||
|
search: searchUsers,
|
||||||
|
result: users
|
||||||
|
}) => (
|
||||||
<OrderDetailsMessages>
|
<OrderDetailsMessages>
|
||||||
{orderMessages => (
|
{orderMessages => (
|
||||||
<OrderOperations
|
<OrderOperations
|
||||||
|
@ -400,12 +404,18 @@ export const OrderDetails: React.StatelessComponent<OrderDetailsProps> = ({
|
||||||
}
|
}
|
||||||
users={maybe(
|
users={maybe(
|
||||||
() =>
|
() =>
|
||||||
users.data.customers.edges.map(
|
users.data.search.edges.map(
|
||||||
edge => edge.node
|
edge => edge.node
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
)}
|
)}
|
||||||
|
hasMore={maybe(
|
||||||
|
() => users.data.search.pageInfo.hasNextPage,
|
||||||
|
false
|
||||||
|
)}
|
||||||
|
onFetchMore={loadMoreCustomers}
|
||||||
fetchUsers={searchUsers}
|
fetchUsers={searchUsers}
|
||||||
|
loading={users.loading}
|
||||||
usersLoading={users.loading}
|
usersLoading={users.loading}
|
||||||
onCustomerEdit={data =>
|
onCustomerEdit={data =>
|
||||||
orderDraftUpdate.mutate({
|
orderDraftUpdate.mutate({
|
||||||
|
@ -511,74 +521,46 @@ export const OrderDetails: React.StatelessComponent<OrderDetailsProps> = ({
|
||||||
variables={DEFAULT_INITIAL_SEARCH_DATA}
|
variables={DEFAULT_INITIAL_SEARCH_DATA}
|
||||||
>
|
>
|
||||||
{({
|
{({
|
||||||
|
loadMore,
|
||||||
search: variantSearch,
|
search: variantSearch,
|
||||||
result: variantSearchOpts
|
result: variantSearchOpts
|
||||||
}) => {
|
}) => (
|
||||||
const fetchMore = () =>
|
<OrderProductAddDialog
|
||||||
variantSearchOpts.loadMore(
|
confirmButtonState={getMutationState(
|
||||||
(prev, next) => {
|
orderLinesAdd.opts.called,
|
||||||
if (
|
orderLinesAdd.opts.loading,
|
||||||
prev.products.pageInfo.endCursor ===
|
maybe(
|
||||||
next.products.pageInfo.endCursor
|
|
||||||
) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
products: {
|
|
||||||
...prev.products,
|
|
||||||
edges: [
|
|
||||||
...prev.products.edges,
|
|
||||||
...next.products.edges
|
|
||||||
],
|
|
||||||
pageInfo: next.products.pageInfo
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
after:
|
|
||||||
variantSearchOpts.data.products
|
|
||||||
.pageInfo.endCursor
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<OrderProductAddDialog
|
|
||||||
confirmButtonState={getMutationState(
|
|
||||||
orderLinesAdd.opts.called,
|
|
||||||
orderLinesAdd.opts.loading,
|
|
||||||
maybe(
|
|
||||||
() =>
|
|
||||||
orderLinesAdd.opts.data
|
|
||||||
.draftOrderLinesCreate.errors
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
loading={variantSearchOpts.loading}
|
|
||||||
open={params.action === "add-order-line"}
|
|
||||||
hasMore={maybe(
|
|
||||||
() =>
|
() =>
|
||||||
variantSearchOpts.data.products
|
orderLinesAdd.opts.data
|
||||||
.pageInfo.hasNextPage
|
.draftOrderLinesCreate.errors
|
||||||
)}
|
)
|
||||||
products={maybe(() =>
|
)}
|
||||||
variantSearchOpts.data.products.edges.map(
|
loading={variantSearchOpts.loading}
|
||||||
edge => edge.node
|
open={params.action === "add-order-line"}
|
||||||
)
|
hasMore={maybe(
|
||||||
)}
|
() =>
|
||||||
onClose={closeModal}
|
variantSearchOpts.data.search.pageInfo
|
||||||
onFetch={variantSearch}
|
.hasNextPage
|
||||||
onFetchMore={fetchMore}
|
)}
|
||||||
onSubmit={variants =>
|
products={maybe(() =>
|
||||||
orderLinesAdd.mutate({
|
variantSearchOpts.data.search.edges.map(
|
||||||
id,
|
edge => edge.node
|
||||||
input: variants.map(variant => ({
|
)
|
||||||
quantity: 1,
|
)}
|
||||||
variantId: variant.id
|
onClose={closeModal}
|
||||||
}))
|
onFetch={variantSearch}
|
||||||
})
|
onFetchMore={loadMore}
|
||||||
}
|
onSubmit={variants =>
|
||||||
/>
|
orderLinesAdd.mutate({
|
||||||
);
|
id,
|
||||||
}}
|
input: variants.map(variant => ({
|
||||||
|
quantity: 1,
|
||||||
|
variantId: variant.id
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</SearchOrderVariant>
|
</SearchOrderVariant>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -38,5 +38,25 @@ export const searchAttributes = gql`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default BaseSearch<SearchAttributes, SearchAttributesVariables>(
|
export default BaseSearch<SearchAttributes, SearchAttributesVariables>(
|
||||||
searchAttributes
|
searchAttributes,
|
||||||
|
result =>
|
||||||
|
result.loadMore(
|
||||||
|
(prev, next) => ({
|
||||||
|
...prev,
|
||||||
|
productType: {
|
||||||
|
...prev.productType,
|
||||||
|
availableAttributes: {
|
||||||
|
...prev.productType.availableAttributes,
|
||||||
|
edges: [
|
||||||
|
...prev.productType.availableAttributes.edges,
|
||||||
|
...next.productType.availableAttributes.edges
|
||||||
|
],
|
||||||
|
pageInfo: next.productType.availableAttributes.pageInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
after: result.data.productType.availableAttributes.pageInfo.endCursor
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import {
|
import {
|
||||||
SearchProductTypes_productTypes_edges_node,
|
SearchProductTypes_search_edges_node,
|
||||||
SearchProductTypes_productTypes_edges_node_productAttributes
|
SearchProductTypes_search_edges_node_productAttributes
|
||||||
} from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
|
} from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
|
||||||
import { AttributeInputTypeEnum } from "../types/globalTypes";
|
import { AttributeInputTypeEnum } from "../types/globalTypes";
|
||||||
import { ProductTypeDetails_productType } from "./types/ProductTypeDetails";
|
import { ProductTypeDetails_productType } from "./types/ProductTypeDetails";
|
||||||
import { ProductTypeList_productTypes_edges_node } from "./types/ProductTypeList";
|
import { ProductTypeList_productTypes_edges_node } from "./types/ProductTypeList";
|
||||||
|
|
||||||
export const attributes: SearchProductTypes_productTypes_edges_node_productAttributes[] = [
|
export const attributes: SearchProductTypes_search_edges_node_productAttributes[] = [
|
||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
__typename: "Attribute" as "Attribute",
|
__typename: "Attribute" as "Attribute",
|
||||||
|
@ -469,8 +469,7 @@ export const attributes: SearchProductTypes_productTypes_edges_node_productAttri
|
||||||
].map(edge => edge.node);
|
].map(edge => edge.node);
|
||||||
|
|
||||||
export const productTypes: Array<
|
export const productTypes: Array<
|
||||||
SearchProductTypes_productTypes_edges_node &
|
SearchProductTypes_search_edges_node & ProductTypeList_productTypes_edges_node
|
||||||
ProductTypeList_productTypes_edges_node
|
|
||||||
> = [
|
> = [
|
||||||
{
|
{
|
||||||
__typename: "ProductType" as "ProductType",
|
__typename: "ProductType" as "ProductType",
|
||||||
|
|
|
@ -13,9 +13,9 @@ import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import SeoForm from "@saleor/components/SeoForm";
|
import SeoForm from "@saleor/components/SeoForm";
|
||||||
import VisibilityCard from "@saleor/components/VisibilityCard";
|
import VisibilityCard from "@saleor/components/VisibilityCard";
|
||||||
import { SearchCategories_categories_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
|
import { SearchCategories_search_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
|
||||||
import { SearchCollections_collections_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
|
import { SearchCollections_search_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
|
||||||
import { SearchProductTypes_productTypes_edges_node_productAttributes } from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
|
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
|
||||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||||
import useFormset from "@saleor/hooks/useFormset";
|
import useFormset from "@saleor/hooks/useFormset";
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
|
@ -27,7 +27,7 @@ import {
|
||||||
} from "@saleor/products/utils/data";
|
} from "@saleor/products/utils/data";
|
||||||
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
||||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
import { UserError } from "../../../types";
|
import { FetchMoreProps, UserError } from "../../../types";
|
||||||
import {
|
import {
|
||||||
createAttributeChangeHandler,
|
createAttributeChangeHandler,
|
||||||
createAttributeMultiChangeHandler,
|
createAttributeMultiChangeHandler,
|
||||||
|
@ -63,15 +63,18 @@ export interface ProductCreatePageSubmitData extends FormData {
|
||||||
|
|
||||||
interface ProductCreatePageProps {
|
interface ProductCreatePageProps {
|
||||||
errors: UserError[];
|
errors: UserError[];
|
||||||
collections: SearchCollections_collections_edges_node[];
|
collections: SearchCollections_search_edges_node[];
|
||||||
categories: SearchCategories_categories_edges_node[];
|
categories: SearchCategories_search_edges_node[];
|
||||||
currency: string;
|
currency: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
fetchMoreCategories: FetchMoreProps;
|
||||||
|
fetchMoreCollections: FetchMoreProps;
|
||||||
|
fetchMoreProductTypes: FetchMoreProps;
|
||||||
productTypes?: Array<{
|
productTypes?: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
productAttributes: SearchProductTypes_productTypes_edges_node_productAttributes[];
|
productAttributes: SearchProductTypes_search_edges_node_productAttributes[];
|
||||||
}>;
|
}>;
|
||||||
header: string;
|
header: string;
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
|
@ -92,6 +95,9 @@ export const ProductCreatePage: React.StatelessComponent<
|
||||||
errors: userErrors,
|
errors: userErrors,
|
||||||
fetchCategories,
|
fetchCategories,
|
||||||
fetchCollections,
|
fetchCollections,
|
||||||
|
fetchMoreCategories,
|
||||||
|
fetchMoreCollections,
|
||||||
|
fetchMoreProductTypes,
|
||||||
header,
|
header,
|
||||||
productTypes: productTypeChoiceList,
|
productTypes: productTypeChoiceList,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
|
@ -271,6 +277,9 @@ export const ProductCreatePage: React.StatelessComponent<
|
||||||
errors={errors}
|
errors={errors}
|
||||||
fetchCategories={fetchCategories}
|
fetchCategories={fetchCategories}
|
||||||
fetchCollections={fetchCollections}
|
fetchCollections={fetchCollections}
|
||||||
|
fetchMoreCategories={fetchMoreCategories}
|
||||||
|
fetchMoreCollections={fetchMoreCollections}
|
||||||
|
fetchMoreProductTypes={fetchMoreProductTypes}
|
||||||
fetchProductTypes={fetchProductTypes}
|
fetchProductTypes={fetchProductTypes}
|
||||||
productType={productType}
|
productType={productType}
|
||||||
productTypeInputDisplayValue={productType.name}
|
productTypeInputDisplayValue={productType.name}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import SingleAutocompleteSelectField, {
|
||||||
} from "@saleor/components/SingleAutocompleteSelectField";
|
} from "@saleor/components/SingleAutocompleteSelectField";
|
||||||
import { ChangeEvent } from "@saleor/hooks/useForm";
|
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import { FormErrors } from "@saleor/types";
|
import { FetchMoreProps, FormErrors } from "@saleor/types";
|
||||||
|
|
||||||
interface ProductType {
|
interface ProductType {
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
|
@ -62,6 +62,9 @@ interface ProductOrganizationProps extends WithStyles<typeof styles> {
|
||||||
productTypes?: SingleAutocompleteChoiceType[];
|
productTypes?: SingleAutocompleteChoiceType[];
|
||||||
fetchCategories: (query: string) => void;
|
fetchCategories: (query: string) => void;
|
||||||
fetchCollections: (query: string) => void;
|
fetchCollections: (query: string) => void;
|
||||||
|
fetchMoreCategories: FetchMoreProps;
|
||||||
|
fetchMoreCollections: FetchMoreProps;
|
||||||
|
fetchMoreProductTypes?: FetchMoreProps;
|
||||||
fetchProductTypes?: (data: string) => void;
|
fetchProductTypes?: (data: string) => void;
|
||||||
onCategoryChange: (event: ChangeEvent) => void;
|
onCategoryChange: (event: ChangeEvent) => void;
|
||||||
onCollectionChange: (event: ChangeEvent) => void;
|
onCollectionChange: (event: ChangeEvent) => void;
|
||||||
|
@ -81,6 +84,9 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
|
||||||
errors,
|
errors,
|
||||||
fetchCategories,
|
fetchCategories,
|
||||||
fetchCollections,
|
fetchCollections,
|
||||||
|
fetchMoreCategories,
|
||||||
|
fetchMoreCollections,
|
||||||
|
fetchMoreProductTypes,
|
||||||
fetchProductTypes,
|
fetchProductTypes,
|
||||||
productType,
|
productType,
|
||||||
productTypeInputDisplayValue,
|
productTypeInputDisplayValue,
|
||||||
|
@ -115,6 +121,7 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
|
||||||
onChange={onProductTypeChange}
|
onChange={onProductTypeChange}
|
||||||
fetchChoices={fetchProductTypes}
|
fetchChoices={fetchProductTypes}
|
||||||
data-tc="product-type"
|
data-tc="product-type"
|
||||||
|
{...fetchMoreProductTypes}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -160,6 +167,7 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
|
||||||
onChange={onCategoryChange}
|
onChange={onCategoryChange}
|
||||||
fetchChoices={fetchCategories}
|
fetchChoices={fetchCategories}
|
||||||
data-tc="category"
|
data-tc="category"
|
||||||
|
{...fetchMoreCategories}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<Hr />
|
<Hr />
|
||||||
|
@ -180,6 +188,7 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
|
||||||
onChange={onCollectionChange}
|
onChange={onCollectionChange}
|
||||||
fetchChoices={fetchCollections}
|
fetchChoices={fetchCollections}
|
||||||
data-tc="collections"
|
data-tc="collections"
|
||||||
|
{...fetchMoreCollections}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -12,14 +12,14 @@ import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import SeoForm from "@saleor/components/SeoForm";
|
import SeoForm from "@saleor/components/SeoForm";
|
||||||
import VisibilityCard from "@saleor/components/VisibilityCard";
|
import VisibilityCard from "@saleor/components/VisibilityCard";
|
||||||
import { SearchCategories_categories_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
|
import { SearchCategories_search_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
|
||||||
import { SearchCollections_collections_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
|
import { SearchCollections_search_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
|
||||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||||
import useFormset from "@saleor/hooks/useFormset";
|
import useFormset from "@saleor/hooks/useFormset";
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import { ListActions, UserError } from "@saleor/types";
|
import { FetchMoreProps, ListActions, UserError } from "@saleor/types";
|
||||||
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
||||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
import {
|
import {
|
||||||
|
@ -50,9 +50,11 @@ import ProductVariants from "../ProductVariants";
|
||||||
export interface ProductUpdatePageProps extends ListActions {
|
export interface ProductUpdatePageProps extends ListActions {
|
||||||
errors: UserError[];
|
errors: UserError[];
|
||||||
placeholderImage: string;
|
placeholderImage: string;
|
||||||
collections: SearchCollections_collections_edges_node[];
|
collections: SearchCollections_search_edges_node[];
|
||||||
categories: SearchCategories_categories_edges_node[];
|
categories: SearchCategories_search_edges_node[];
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
fetchMoreCategories: FetchMoreProps;
|
||||||
|
fetchMoreCollections: FetchMoreProps;
|
||||||
variants: ProductDetails_product_variants[];
|
variants: ProductDetails_product_variants[];
|
||||||
images: ProductDetails_product_images[];
|
images: ProductDetails_product_images[];
|
||||||
product: ProductDetails_product;
|
product: ProductDetails_product;
|
||||||
|
@ -86,6 +88,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
errors: userErrors,
|
errors: userErrors,
|
||||||
fetchCategories,
|
fetchCategories,
|
||||||
fetchCollections,
|
fetchCollections,
|
||||||
|
fetchMoreCategories,
|
||||||
|
fetchMoreCollections,
|
||||||
images,
|
images,
|
||||||
header,
|
header,
|
||||||
placeholderImage,
|
placeholderImage,
|
||||||
|
@ -285,6 +289,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
errors={errors}
|
errors={errors}
|
||||||
fetchCategories={fetchCategories}
|
fetchCategories={fetchCategories}
|
||||||
fetchCollections={fetchCollections}
|
fetchCollections={fetchCollections}
|
||||||
|
fetchMoreCategories={fetchMoreCategories}
|
||||||
|
fetchMoreCollections={fetchMoreCollections}
|
||||||
productType={maybe(() => product.productType)}
|
productType={maybe(() => product.productType)}
|
||||||
onCategoryChange={handleCategorySelect}
|
onCategoryChange={handleCategorySelect}
|
||||||
onCollectionChange={handleCollectionSelect}
|
onCollectionChange={handleCollectionSelect}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { RawDraftContentState } from "draft-js";
|
||||||
|
|
||||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||||
import { SearchProductTypes_productTypes_edges_node_productAttributes } from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
|
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import {
|
import {
|
||||||
ProductDetails_product,
|
ProductDetails_product,
|
||||||
|
@ -35,7 +35,7 @@ export interface ProductType {
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
productAttributes: SearchProductTypes_productTypes_edges_node_productAttributes[];
|
productAttributes: SearchProductTypes_search_edges_node_productAttributes[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAttributeInputFromProduct(
|
export function getAttributeInputFromProduct(
|
||||||
|
|
|
@ -33,11 +33,20 @@ export const ProductUpdate: React.StatelessComponent<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
||||||
{({ search: searchCategory, result: searchCategoryOpts }) => (
|
{({
|
||||||
|
loadMore: loadMoreCategories,
|
||||||
|
search: searchCategory,
|
||||||
|
result: searchCategoryOpts
|
||||||
|
}) => (
|
||||||
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
||||||
{({ search: searchCollection, result: searchCollectionOpts }) => (
|
{({
|
||||||
|
loadMore: loadMoreCollections,
|
||||||
|
search: searchCollection,
|
||||||
|
result: searchCollectionOpts
|
||||||
|
}) => (
|
||||||
<SearchProductTypes variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
<SearchProductTypes variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
||||||
{({
|
{({
|
||||||
|
loadMore: loadMoreProductTypes,
|
||||||
search: searchProductTypes,
|
search: searchProductTypes,
|
||||||
result: searchProductTypesOpts
|
result: searchProductTypesOpts
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -121,11 +130,11 @@ export const ProductUpdate: React.StatelessComponent<
|
||||||
<ProductCreatePage
|
<ProductCreatePage
|
||||||
currency={maybe(() => shop.defaultCurrency)}
|
currency={maybe(() => shop.defaultCurrency)}
|
||||||
categories={maybe(
|
categories={maybe(
|
||||||
() => searchCategoryOpts.data.categories.edges,
|
() => searchCategoryOpts.data.search.edges,
|
||||||
[]
|
[]
|
||||||
).map(edge => edge.node)}
|
).map(edge => edge.node)}
|
||||||
collections={maybe(
|
collections={maybe(
|
||||||
() => searchCollectionOpts.data.collections.edges,
|
() => searchCollectionOpts.data.search.edges,
|
||||||
[]
|
[]
|
||||||
).map(edge => edge.node)}
|
).map(edge => edge.node)}
|
||||||
disabled={productCreateDataLoading}
|
disabled={productCreateDataLoading}
|
||||||
|
@ -141,13 +150,40 @@ export const ProductUpdate: React.StatelessComponent<
|
||||||
description: "page header"
|
description: "page header"
|
||||||
})}
|
})}
|
||||||
productTypes={maybe(() =>
|
productTypes={maybe(() =>
|
||||||
searchProductTypesOpts.data.productTypes.edges.map(
|
searchProductTypesOpts.data.search.edges.map(
|
||||||
edge => edge.node
|
edge => edge.node
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
saveButtonBarState={formTransitionState}
|
saveButtonBarState={formTransitionState}
|
||||||
|
fetchMoreCategories={{
|
||||||
|
hasMore: maybe(
|
||||||
|
() =>
|
||||||
|
searchCategoryOpts.data.search.pageInfo
|
||||||
|
.hasNextPage
|
||||||
|
),
|
||||||
|
loading: searchCategoryOpts.loading,
|
||||||
|
onFetchMore: loadMoreCategories
|
||||||
|
}}
|
||||||
|
fetchMoreCollections={{
|
||||||
|
hasMore: maybe(
|
||||||
|
() =>
|
||||||
|
searchCollectionOpts.data.search.pageInfo
|
||||||
|
.hasNextPage
|
||||||
|
),
|
||||||
|
loading: searchCollectionOpts.loading,
|
||||||
|
onFetchMore: loadMoreCollections
|
||||||
|
}}
|
||||||
|
fetchMoreProductTypes={{
|
||||||
|
hasMore: maybe(
|
||||||
|
() =>
|
||||||
|
searchProductTypesOpts.data.search.pageInfo
|
||||||
|
.hasNextPage
|
||||||
|
),
|
||||||
|
loading: searchProductTypesOpts.loading,
|
||||||
|
onFetchMore: loadMoreProductTypes
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -68,9 +68,17 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
||||||
{({ search: searchCategories, result: searchCategoriesOpts }) => (
|
{({
|
||||||
|
loadMore: loadMoreCategories,
|
||||||
|
search: searchCategories,
|
||||||
|
result: searchCategoriesOpts
|
||||||
|
}) => (
|
||||||
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
||||||
{({ search: searchCollections, result: searchCollectionsOpts }) => (
|
{({
|
||||||
|
loadMore: loadMoreCollections,
|
||||||
|
search: searchCollections,
|
||||||
|
result: searchCollectionsOpts
|
||||||
|
}) => (
|
||||||
<TypedProductDetailsQuery
|
<TypedProductDetailsQuery
|
||||||
displayLoader
|
displayLoader
|
||||||
require={["product"]}
|
require={["product"]}
|
||||||
|
@ -228,11 +236,11 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const categories = maybe(
|
const categories = maybe(
|
||||||
() => searchCategoriesOpts.data.categories.edges,
|
() => searchCategoriesOpts.data.search.edges,
|
||||||
[]
|
[]
|
||||||
).map(edge => edge.node);
|
).map(edge => edge.node);
|
||||||
const collections = maybe(
|
const collections = maybe(
|
||||||
() => searchCollectionsOpts.data.collections.edges,
|
() => searchCollectionsOpts.data.search.edges,
|
||||||
[]
|
[]
|
||||||
).map(edge => edge.node);
|
).map(edge => edge.node);
|
||||||
const errors = maybe(
|
const errors = maybe(
|
||||||
|
@ -295,6 +303,24 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
|
||||||
selected={listElements.length}
|
selected={listElements.length}
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
toggleAll={toggleAll}
|
toggleAll={toggleAll}
|
||||||
|
fetchMoreCategories={{
|
||||||
|
hasMore: maybe(
|
||||||
|
() =>
|
||||||
|
searchCategoriesOpts.data.search.pageInfo
|
||||||
|
.hasNextPage
|
||||||
|
),
|
||||||
|
loading: searchCategoriesOpts.loading,
|
||||||
|
onFetchMore: loadMoreCategories
|
||||||
|
}}
|
||||||
|
fetchMoreCollections={{
|
||||||
|
hasMore: maybe(
|
||||||
|
() =>
|
||||||
|
searchCollectionsOpts.data.search.pageInfo
|
||||||
|
.hasNextPage
|
||||||
|
),
|
||||||
|
loading: searchCollectionsOpts.loading,
|
||||||
|
onFetchMore: loadMoreCollections
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ActionDialog
|
<ActionDialog
|
||||||
open={params.action === "remove"}
|
open={params.action === "remove"}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { RequireAtLeastOne } from "./misc";
|
||||||
export interface LoadMore<TData, TVariables> {
|
export interface LoadMore<TData, TVariables> {
|
||||||
loadMore: (
|
loadMore: (
|
||||||
mergeFunc: (prev: TData, next: TData) => TData,
|
mergeFunc: (prev: TData, next: TData) => TData,
|
||||||
extraVariables: RequireAtLeastOne<TVariables>
|
extraVariables: Partial<TVariables>
|
||||||
) => Promise<ApolloQueryResult<TData>>;
|
) => Promise<ApolloQueryResult<TData>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ const CardDecorator = storyFn => (
|
||||||
style={{
|
style={{
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
|
position: "relative",
|
||||||
width: 400
|
width: 400
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -29,7 +29,6 @@ function loadStories() {
|
||||||
require("./stories/components/Filter");
|
require("./stories/components/Filter");
|
||||||
require("./stories/components/Money");
|
require("./stories/components/Money");
|
||||||
require("./stories/components/MoneyRange");
|
require("./stories/components/MoneyRange");
|
||||||
require("./stories/components/MultiAutocompleteSelectField");
|
|
||||||
require("./stories/components/MultiSelectField");
|
require("./stories/components/MultiSelectField");
|
||||||
require("./stories/components/NotFoundPage");
|
require("./stories/components/NotFoundPage");
|
||||||
require("./stories/components/PageHeader");
|
require("./stories/components/PageHeader");
|
||||||
|
@ -39,7 +38,6 @@ function loadStories() {
|
||||||
require("./stories/components/RichTextEditor");
|
require("./stories/components/RichTextEditor");
|
||||||
require("./stories/components/SaveButtonBar");
|
require("./stories/components/SaveButtonBar");
|
||||||
require("./stories/components/SaveFilterTabDialog");
|
require("./stories/components/SaveFilterTabDialog");
|
||||||
require("./stories/components/SingleAutocompleteSelectField");
|
|
||||||
require("./stories/components/SingleSelectField");
|
require("./stories/components/SingleSelectField");
|
||||||
require("./stories/components/Skeleton");
|
require("./stories/components/Skeleton");
|
||||||
require("./stories/components/StatusLabel");
|
require("./stories/components/StatusLabel");
|
||||||
|
@ -125,7 +123,6 @@ function loadStories() {
|
||||||
require("./stories/orders/OrderBulkCancelDialog");
|
require("./stories/orders/OrderBulkCancelDialog");
|
||||||
require("./stories/orders/OrderCancelDialog");
|
require("./stories/orders/OrderCancelDialog");
|
||||||
require("./stories/orders/OrderCustomer");
|
require("./stories/orders/OrderCustomer");
|
||||||
require("./stories/orders/OrderCustomerEditDialog");
|
|
||||||
require("./stories/orders/OrderDetailsPage");
|
require("./stories/orders/OrderDetailsPage");
|
||||||
require("./stories/orders/OrderDraftCancelDialog");
|
require("./stories/orders/OrderDraftCancelDialog");
|
||||||
require("./stories/orders/OrderDraftFinalizeDialog");
|
require("./stories/orders/OrderDraftFinalizeDialog");
|
||||||
|
|
|
@ -1,38 +1,41 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent";
|
||||||
|
|
||||||
interface ChoiceProviderProps {
|
interface ChoiceProviderProps {
|
||||||
children: ((
|
children: (props: {
|
||||||
props: {
|
choices: SingleAutocompleteChoiceType[];
|
||||||
choices: Array<{
|
hasMore: boolean;
|
||||||
label: string;
|
loading: boolean;
|
||||||
value: string;
|
fetchChoices: (value: string) => void;
|
||||||
}>;
|
fetchMore: () => void;
|
||||||
loading: boolean;
|
}) => React.ReactElement<any>;
|
||||||
fetchChoices(value: string);
|
choices: SingleAutocompleteChoiceType[];
|
||||||
}
|
|
||||||
) => React.ReactElement<any>);
|
|
||||||
choices: Array<{
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
interface ChoiceProviderState {
|
interface ChoiceProviderState {
|
||||||
choices: Array<{
|
choices: SingleAutocompleteChoiceType[];
|
||||||
label: string;
|
filteredChoices: SingleAutocompleteChoiceType[];
|
||||||
value: string;
|
first: number;
|
||||||
}>;
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
timeout: any;
|
timeout: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const step = 5;
|
||||||
|
|
||||||
export class ChoiceProvider extends React.Component<
|
export class ChoiceProvider extends React.Component<
|
||||||
ChoiceProviderProps,
|
ChoiceProviderProps,
|
||||||
ChoiceProviderState
|
ChoiceProviderState
|
||||||
> {
|
> {
|
||||||
state = { choices: [], loading: false, timeout: null };
|
state = {
|
||||||
|
choices: [],
|
||||||
|
filteredChoices: [],
|
||||||
|
first: step,
|
||||||
|
loading: false,
|
||||||
|
timeout: null
|
||||||
|
};
|
||||||
|
|
||||||
handleChange = inputValue => {
|
handleChange = (inputValue: string) => {
|
||||||
if (this.state.loading) {
|
if (!!this.state.timeout) {
|
||||||
clearTimeout(this.state.timeout);
|
clearTimeout(this.state.timeout);
|
||||||
}
|
}
|
||||||
const timeout = setTimeout(() => this.fetchChoices(inputValue), 500);
|
const timeout = setTimeout(() => this.fetchChoices(inputValue), 500);
|
||||||
|
@ -42,22 +45,35 @@ export class ChoiceProvider extends React.Component<
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchChoices = inputValue => {
|
handleFetchMore = () => {
|
||||||
let count = 0;
|
if (!!this.state.timeout) {
|
||||||
|
clearTimeout(this.state.timeout);
|
||||||
|
}
|
||||||
|
const timeout = setTimeout(this.fetchMore, 500);
|
||||||
this.setState({
|
this.setState({
|
||||||
choices: this.props.choices.filter(suggestion => {
|
loading: true,
|
||||||
const keep =
|
timeout
|
||||||
(!inputValue ||
|
});
|
||||||
suggestion.label.toLowerCase().indexOf(inputValue.toLowerCase()) !==
|
};
|
||||||
-1) &&
|
|
||||||
count < 5;
|
|
||||||
|
|
||||||
if (keep) {
|
fetchMore = () =>
|
||||||
count += 1;
|
this.setState(prevState => ({
|
||||||
}
|
filteredChoices: prevState.choices.slice(0, prevState.first + step),
|
||||||
|
first: prevState.first + step,
|
||||||
|
loading: false,
|
||||||
|
timeout: null
|
||||||
|
}));
|
||||||
|
|
||||||
return keep;
|
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,
|
loading: false,
|
||||||
timeout: null
|
timeout: null
|
||||||
});
|
});
|
||||||
|
@ -65,8 +81,10 @@ export class ChoiceProvider extends React.Component<
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.props.children({
|
return this.props.children({
|
||||||
choices: this.state.choices,
|
choices: this.state.filteredChoices,
|
||||||
fetchChoices: this.handleChange,
|
fetchChoices: this.handleChange,
|
||||||
|
fetchMore: this.handleFetchMore,
|
||||||
|
hasMore: this.state.choices.length > this.state.filteredChoices.length,
|
||||||
loading: this.state.loading
|
loading: this.state.loading
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
import { storiesOf } from "@storybook/react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import MultiAutocompleteSelectField, {
|
|
||||||
MultiAutocompleteSelectFieldProps
|
|
||||||
} from "@saleor/components/MultiAutocompleteSelectField";
|
|
||||||
import useMultiAutocomplete from "@saleor/hooks/useMultiAutocomplete";
|
|
||||||
import CardDecorator from "../../CardDecorator";
|
|
||||||
import Decorator from "../../Decorator";
|
|
||||||
import { ChoiceProvider } from "../../mock";
|
|
||||||
|
|
||||||
const suggestions = [
|
|
||||||
"Afghanistan",
|
|
||||||
"Burundi",
|
|
||||||
"Comoros",
|
|
||||||
"Egypt",
|
|
||||||
"Equatorial Guinea",
|
|
||||||
"Greenland",
|
|
||||||
"Isle of Man",
|
|
||||||
"Israel",
|
|
||||||
"Italy",
|
|
||||||
"United States",
|
|
||||||
"Wallis and Futuna",
|
|
||||||
"Zimbabwe"
|
|
||||||
].map(c => ({ label: c, value: c.toLocaleLowerCase().replace(/\s+/, "_") }));
|
|
||||||
|
|
||||||
const props: MultiAutocompleteSelectFieldProps = {
|
|
||||||
choices: undefined,
|
|
||||||
displayValues: [],
|
|
||||||
label: "Country",
|
|
||||||
loading: false,
|
|
||||||
name: "country",
|
|
||||||
onChange: () => undefined,
|
|
||||||
placeholder: "Select country",
|
|
||||||
value: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
const Story: React.FC<
|
|
||||||
Partial<MultiAutocompleteSelectFieldProps>
|
|
||||||
> = storyProps => {
|
|
||||||
const { change, data: countries } = useMultiAutocomplete([suggestions[0]]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChoiceProvider choices={suggestions}>
|
|
||||||
{({ choices, loading, fetchChoices }) => (
|
|
||||||
<MultiAutocompleteSelectField
|
|
||||||
{...props}
|
|
||||||
displayValues={countries}
|
|
||||||
choices={choices}
|
|
||||||
fetchChoices={fetchChoices}
|
|
||||||
helperText={`Value: ${countries
|
|
||||||
.map(country => country.label)
|
|
||||||
.join(", ")}`}
|
|
||||||
onChange={event => change(event, choices)}
|
|
||||||
value={countries.map(country => country.value)}
|
|
||||||
loading={loading}
|
|
||||||
{...storyProps}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ChoiceProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
storiesOf("Generics / MultiAutocompleteSelectField", module)
|
|
||||||
.addDecorator(CardDecorator)
|
|
||||||
.addDecorator(Decorator)
|
|
||||||
.add("with loaded data", () => <Story />)
|
|
||||||
.add("with loading data", () => <Story loading={true} />)
|
|
||||||
.add("with custom option", () => <Story allowCustomValues={true} />);
|
|
|
@ -1,81 +0,0 @@
|
||||||
import { storiesOf } from "@storybook/react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import Form from "@saleor/components/Form";
|
|
||||||
import SingleAutocompleteSelectField, {
|
|
||||||
SingleAutocompleteSelectFieldProps
|
|
||||||
} from "@saleor/components/SingleAutocompleteSelectField";
|
|
||||||
import { maybe } from "@saleor/misc";
|
|
||||||
import CardDecorator from "../../CardDecorator";
|
|
||||||
import Decorator from "../../Decorator";
|
|
||||||
import { ChoiceProvider } from "../../mock";
|
|
||||||
|
|
||||||
const suggestions = [
|
|
||||||
"Afghanistan",
|
|
||||||
"Burundi",
|
|
||||||
"Comoros",
|
|
||||||
"Egypt",
|
|
||||||
"Equatorial Guinea",
|
|
||||||
"Greenland",
|
|
||||||
"Isle of Man",
|
|
||||||
"Israel",
|
|
||||||
"Italy",
|
|
||||||
"United States",
|
|
||||||
"Wallis and Futuna",
|
|
||||||
"Zimbabwe"
|
|
||||||
].map(c => ({ label: c, value: c.toLocaleLowerCase().replace(/\s+/, "_") }));
|
|
||||||
|
|
||||||
const props: SingleAutocompleteSelectFieldProps = {
|
|
||||||
choices: undefined,
|
|
||||||
displayValue: undefined,
|
|
||||||
label: "Country",
|
|
||||||
loading: false,
|
|
||||||
name: "country",
|
|
||||||
onChange: () => undefined,
|
|
||||||
placeholder: "Select country"
|
|
||||||
};
|
|
||||||
|
|
||||||
const Story: React.FC<
|
|
||||||
Partial<SingleAutocompleteSelectFieldProps>
|
|
||||||
> = storyProps => {
|
|
||||||
const [displayValue, setDisplayValue] = React.useState(suggestions[0].label);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form initial={{ country: suggestions[0].value }}>
|
|
||||||
{({ change, data }) => (
|
|
||||||
<ChoiceProvider choices={suggestions}>
|
|
||||||
{({ choices, loading, fetchChoices }) => {
|
|
||||||
const handleSelect = (event: React.ChangeEvent<any>) => {
|
|
||||||
const value: string = event.target.value;
|
|
||||||
const match = choices.find(choice => choice.value === value);
|
|
||||||
const label = maybe(() => match.label, value);
|
|
||||||
setDisplayValue(label);
|
|
||||||
change(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SingleAutocompleteSelectField
|
|
||||||
{...props}
|
|
||||||
displayValue={displayValue}
|
|
||||||
choices={choices}
|
|
||||||
fetchChoices={fetchChoices}
|
|
||||||
helperText={`Value: ${data.country}`}
|
|
||||||
onChange={handleSelect}
|
|
||||||
value={data.country}
|
|
||||||
loading={loading}
|
|
||||||
{...storyProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</ChoiceProvider>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
storiesOf("Generics / SingleAutocompleteSelectField", module)
|
|
||||||
.addDecorator(CardDecorator)
|
|
||||||
.addDecorator(Decorator)
|
|
||||||
.add("with loaded data", () => <Story />)
|
|
||||||
.add("with loading data", () => <Story loading={true} />)
|
|
||||||
.add("with custom option", () => <Story allowCustomValues={true} />);
|
|
|
@ -11,7 +11,10 @@ import Decorator from "../../Decorator";
|
||||||
const props: CustomerAddressDialogProps = {
|
const props: CustomerAddressDialogProps = {
|
||||||
address: customer.addresses[0],
|
address: customer.addresses[0],
|
||||||
confirmButtonState: "default",
|
confirmButtonState: "default",
|
||||||
countries,
|
countries: countries.map(c => ({
|
||||||
|
code: c.code,
|
||||||
|
label: c.name
|
||||||
|
})),
|
||||||
errors: [],
|
errors: [],
|
||||||
onClose: () => undefined,
|
onClose: () => undefined,
|
||||||
onConfirm: () => undefined,
|
onConfirm: () => undefined,
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { storiesOf } from "@storybook/react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import OrderCustomerEditDialog from "../../../orders/components/OrderCustomerEditDialog";
|
|
||||||
import { clients as users } from "../../../orders/fixtures";
|
|
||||||
import Decorator from "../../Decorator";
|
|
||||||
|
|
||||||
const user = users[0];
|
|
||||||
|
|
||||||
storiesOf("Orders / OrderCustomerEditDialog", module)
|
|
||||||
.addDecorator(Decorator)
|
|
||||||
.add("default", () => (
|
|
||||||
<OrderCustomerEditDialog
|
|
||||||
confirmButtonState="default"
|
|
||||||
fetchUsers={undefined}
|
|
||||||
onChange={undefined}
|
|
||||||
onClose={undefined}
|
|
||||||
onConfirm={undefined}
|
|
||||||
open={true}
|
|
||||||
user={user.id}
|
|
||||||
userDisplayValue={user.email}
|
|
||||||
users={users}
|
|
||||||
/>
|
|
||||||
));
|
|
|
@ -3,6 +3,7 @@ import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import placeholderImage from "@assets/images/placeholder60x60.png";
|
import placeholderImage from "@assets/images/placeholder60x60.png";
|
||||||
|
import { fetchMoreProps } from "@saleor/fixtures";
|
||||||
import OrderDraftPage, {
|
import OrderDraftPage, {
|
||||||
OrderDraftPageProps
|
OrderDraftPageProps
|
||||||
} from "../../../orders/components/OrderDraftPage";
|
} from "../../../orders/components/OrderDraftPage";
|
||||||
|
@ -12,6 +13,7 @@ import Decorator from "../../Decorator";
|
||||||
const order = draftOrder(placeholderImage);
|
const order = draftOrder(placeholderImage);
|
||||||
|
|
||||||
const props: Omit<OrderDraftPageProps, "classes"> = {
|
const props: Omit<OrderDraftPageProps, "classes"> = {
|
||||||
|
...fetchMoreProps,
|
||||||
countries,
|
countries,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
fetchUsers: () => undefined,
|
fetchUsers: () => undefined,
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { fetchMoreProps } from "@saleor/fixtures";
|
||||||
import ProductCreatePage, {
|
import ProductCreatePage, {
|
||||||
ProductCreatePageSubmitData
|
ProductCreatePageSubmitData
|
||||||
} from "../../../products/components/ProductCreatePage";
|
} from "../../../products/components/ProductCreatePage";
|
||||||
|
|
||||||
import { formError } from "../../misc";
|
|
||||||
|
|
||||||
import { product as productFixture } from "../../../products/fixtures";
|
import { product as productFixture } from "../../../products/fixtures";
|
||||||
import { productTypes } from "../../../productTypes/fixtures";
|
import { productTypes } from "../../../productTypes/fixtures";
|
||||||
import Decorator from "../../Decorator";
|
import Decorator from "../../Decorator";
|
||||||
|
import { formError } from "../../misc";
|
||||||
|
|
||||||
const product = productFixture("");
|
const product = productFixture("");
|
||||||
|
|
||||||
|
@ -25,6 +24,9 @@ storiesOf("Views / Products / Create product", module)
|
||||||
fetchCategories={() => undefined}
|
fetchCategories={() => undefined}
|
||||||
fetchCollections={() => undefined}
|
fetchCollections={() => undefined}
|
||||||
fetchProductTypes={() => undefined}
|
fetchProductTypes={() => undefined}
|
||||||
|
fetchMoreCategories={fetchMoreProps}
|
||||||
|
fetchMoreCollections={fetchMoreProps}
|
||||||
|
fetchMoreProductTypes={fetchMoreProps}
|
||||||
productTypes={productTypes}
|
productTypes={productTypes}
|
||||||
categories={[product.category]}
|
categories={[product.category]}
|
||||||
onBack={() => undefined}
|
onBack={() => undefined}
|
||||||
|
@ -42,6 +44,9 @@ storiesOf("Views / Products / Create product", module)
|
||||||
fetchCategories={() => undefined}
|
fetchCategories={() => undefined}
|
||||||
fetchCollections={() => undefined}
|
fetchCollections={() => undefined}
|
||||||
fetchProductTypes={() => undefined}
|
fetchProductTypes={() => undefined}
|
||||||
|
fetchMoreCategories={fetchMoreProps}
|
||||||
|
fetchMoreCollections={fetchMoreProps}
|
||||||
|
fetchMoreProductTypes={fetchMoreProps}
|
||||||
productTypes={productTypes}
|
productTypes={productTypes}
|
||||||
categories={[product.category]}
|
categories={[product.category]}
|
||||||
onBack={() => undefined}
|
onBack={() => undefined}
|
||||||
|
@ -61,6 +66,9 @@ storiesOf("Views / Products / Create product", module)
|
||||||
fetchCategories={() => undefined}
|
fetchCategories={() => undefined}
|
||||||
fetchCollections={() => undefined}
|
fetchCollections={() => undefined}
|
||||||
fetchProductTypes={() => undefined}
|
fetchProductTypes={() => undefined}
|
||||||
|
fetchMoreCategories={fetchMoreProps}
|
||||||
|
fetchMoreCollections={fetchMoreProps}
|
||||||
|
fetchMoreProductTypes={fetchMoreProps}
|
||||||
productTypes={productTypes}
|
productTypes={productTypes}
|
||||||
categories={[product.category]}
|
categories={[product.category]}
|
||||||
onBack={() => undefined}
|
onBack={() => undefined}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React from "react";
|
||||||
|
|
||||||
import placeholderImage from "@assets/images/placeholder255x255.png";
|
import placeholderImage from "@assets/images/placeholder255x255.png";
|
||||||
import { collections } from "@saleor/collections/fixtures";
|
import { collections } from "@saleor/collections/fixtures";
|
||||||
import { listActionsProps } from "@saleor/fixtures";
|
import { fetchMoreProps, listActionsProps } from "@saleor/fixtures";
|
||||||
import ProductUpdatePage, {
|
import ProductUpdatePage, {
|
||||||
ProductUpdatePageProps
|
ProductUpdatePageProps
|
||||||
} from "@saleor/products/components/ProductUpdatePage";
|
} from "@saleor/products/components/ProductUpdatePage";
|
||||||
|
@ -20,6 +20,8 @@ const props: ProductUpdatePageProps = {
|
||||||
errors: [],
|
errors: [],
|
||||||
fetchCategories: () => undefined,
|
fetchCategories: () => undefined,
|
||||||
fetchCollections: () => undefined,
|
fetchCollections: () => undefined,
|
||||||
|
fetchMoreCategories: fetchMoreProps,
|
||||||
|
fetchMoreCollections: fetchMoreProps,
|
||||||
header: product.name,
|
header: product.name,
|
||||||
images: product.images,
|
images: product.images,
|
||||||
onBack: () => undefined,
|
onBack: () => undefined,
|
||||||
|
|
|
@ -10,7 +10,8 @@ function createSingleAutocompleteSelectHandler(
|
||||||
change(event);
|
change(event);
|
||||||
|
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
setSelected(choices.find(category => category.value === value).label);
|
const choice = choices.find(category => category.value === value)
|
||||||
|
setSelected(choice ? choice.label : value);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue