Allow all attributes to appear in grid in PLP (#1933)
* Remove unnecessary attribute filtering * Enable all attribute types to be displayed in plp * Improve attribute rendering in plp * Remove obsolete filters * Add story * Rmove dashboard settings section * Update snapshots * Remove unused import * Add column search * Fix type * Update messages * Allow popper to appear on top of select * Update snapshots * Update label * Use autocomplete from macaw * Fix stories * Remove unused imports * Update macaw * Update message * Update messages and snapshots
This commit is contained in:
parent
bd15c52ee5
commit
8fc48eb5f2
38 changed files with 4437 additions and 3202 deletions
1642
introspection.json
1642
introspection.json
File diff suppressed because it is too large
Load diff
|
@ -1042,14 +1042,6 @@
|
|||
"src_dot_attributes_dot_components_dot_AttributeListPage_dot_3916653510": {
|
||||
"string": "Search Attribute"
|
||||
},
|
||||
"src_dot_attributes_dot_components_dot_AttributeListPage_dot_availableInGrid": {
|
||||
"context": "attribute can be column in product list table",
|
||||
"string": "Can be used as column"
|
||||
},
|
||||
"src_dot_attributes_dot_components_dot_AttributeListPage_dot_filterableInDashboard": {
|
||||
"context": "use attribute in filtering",
|
||||
"string": "Filterable in Dashboard"
|
||||
},
|
||||
"src_dot_attributes_dot_components_dot_AttributeListPage_dot_filterableInStorefront": {
|
||||
"context": "use attribute in filtering",
|
||||
"string": "Filterable in Storefront"
|
||||
|
@ -1069,9 +1061,9 @@
|
|||
"src_dot_attributes_dot_components_dot_AttributeList_dot_1192828581": {
|
||||
"string": "No attributes found"
|
||||
},
|
||||
"src_dot_attributes_dot_components_dot_AttributeList_dot_2186555805": {
|
||||
"src_dot_attributes_dot_components_dot_AttributeList_dot_1708933569": {
|
||||
"context": "attribute can be searched in storefront",
|
||||
"string": "Use in faceted search"
|
||||
"string": "Use as filter"
|
||||
},
|
||||
"src_dot_attributes_dot_components_dot_AttributeList_dot_2235596452": {
|
||||
"context": "attribute can be searched in dashboard",
|
||||
|
@ -1132,7 +1124,7 @@
|
|||
},
|
||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_filterableInStorefront": {
|
||||
"context": "attribute is filterable in storefront",
|
||||
"string": "Use in Faceted Navigation"
|
||||
"string": "Use as filter"
|
||||
},
|
||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_storefrontPropertiesTitle": {
|
||||
"context": "attribute properties regarding storefront",
|
||||
|
@ -2102,17 +2094,17 @@
|
|||
"context": "Status label when object is unpublished in a channel",
|
||||
"string": "Unpublished"
|
||||
},
|
||||
"src_dot_components_dot_ColumnPicker_dot_1483881697": {
|
||||
"context": "button",
|
||||
"string": "Reset"
|
||||
"src_dot_components_dot_ColumnPicker_dot_columnLabel": {
|
||||
"context": "input label",
|
||||
"string": "Selected columns"
|
||||
},
|
||||
"src_dot_components_dot_ColumnPicker_dot_2539195044": {
|
||||
"context": "select visible columns button",
|
||||
"string": "Columns"
|
||||
"src_dot_components_dot_ColumnPicker_dot_columnSubheader": {
|
||||
"context": "header",
|
||||
"string": "Column settings"
|
||||
},
|
||||
"src_dot_components_dot_ColumnPicker_dot_2715399461": {
|
||||
"context": "pick columns to display",
|
||||
"string": "{numberOfSelected} columns selected out of {numberOfTotal}"
|
||||
"src_dot_components_dot_ColumnPicker_dot_title": {
|
||||
"context": "header",
|
||||
"string": "Customize list"
|
||||
},
|
||||
"src_dot_components_dot_CompanyAddressInput_dot_1139500589": {
|
||||
"string": "Country"
|
||||
|
@ -7115,6 +7107,10 @@
|
|||
"src_dot_requiredField": {
|
||||
"string": "This field is required"
|
||||
},
|
||||
"src_dot_reset": {
|
||||
"context": "button",
|
||||
"string": "Reset"
|
||||
},
|
||||
"src_dot_returned": {
|
||||
"context": "order status",
|
||||
"string": "Returned"
|
||||
|
|
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -4828,11 +4828,12 @@
|
|||
}
|
||||
},
|
||||
"@saleor/macaw-ui": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.4.0.tgz",
|
||||
"integrity": "sha512-N0DUNByS72juCmoCjUO5CChp53VxsZGz99rpYsdID06LMZndlu/ZoBmKsJv1mZcqUCB7BIqh9hntNd6MDPHpCA==",
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.5.0.tgz",
|
||||
"integrity": "sha512-1dTgbmBHplWpqqyX7kZZSVMz2FESNeUerD5AXPfRYpE6Cr0L+oEfY4NFZlhe2dTY8hPeLz+hPv36JLlD/sGWyA==",
|
||||
"requires": {
|
||||
"clsx": "^1.1.1",
|
||||
"downshift": "^6.1.7",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react-inlinesvg": "^2.3.0"
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.58",
|
||||
"@material-ui/styles": "^4.11.4",
|
||||
"@saleor/macaw-ui": "^0.4.0",
|
||||
"@saleor/macaw-ui": "^0.5.0",
|
||||
"@saleor/sdk": "^0.4.4",
|
||||
"@sentry/react": "^6.0.0",
|
||||
"@types/faker": "^5.1.6",
|
||||
|
|
3452
schema.graphql
3452
schema.graphql
File diff suppressed because it is too large
Load diff
|
@ -158,7 +158,7 @@ const AttributeList: React.FC<AttributeListProps> = ({
|
|||
onClick={() => onSort(AttributeListUrlSortField.useInFacetedSearch)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Use in faceted search"
|
||||
defaultMessage="Use as filter"
|
||||
description="attribute can be searched in storefront"
|
||||
/>
|
||||
</TableCellHeader>
|
||||
|
|
|
@ -5,8 +5,6 @@ import { createBooleanField } from "@saleor/utils/filters/fields";
|
|||
import { defineMessages, IntlShape } from "react-intl";
|
||||
|
||||
export enum AttributeFilterKeys {
|
||||
availableInGrid = "availableInGrid",
|
||||
filterableInDashboard = "filterableInDashboard",
|
||||
filterableInStorefront = "filterableInStorefront",
|
||||
isVariantOnly = "isVariantOnly",
|
||||
valueRequired = "valueRequired",
|
||||
|
@ -14,8 +12,6 @@ export enum AttributeFilterKeys {
|
|||
}
|
||||
|
||||
export interface AttributeListFilterOpts {
|
||||
availableInGrid: FilterOpts<boolean>;
|
||||
filterableInDashboard: FilterOpts<boolean>;
|
||||
filterableInStorefront: FilterOpts<boolean>;
|
||||
isVariantOnly: FilterOpts<boolean>;
|
||||
valueRequired: FilterOpts<boolean>;
|
||||
|
@ -23,14 +19,6 @@ export interface AttributeListFilterOpts {
|
|||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
availableInGrid: {
|
||||
defaultMessage: "Can be used as column",
|
||||
description: "attribute can be column in product list table"
|
||||
},
|
||||
filterableInDashboard: {
|
||||
defaultMessage: "Filterable in Dashboard",
|
||||
description: "use attribute in filtering"
|
||||
},
|
||||
filterableInStorefront: {
|
||||
defaultMessage: "Filterable in Storefront",
|
||||
description: "use attribute in filtering"
|
||||
|
@ -54,30 +42,6 @@ export function createFilterStructure(
|
|||
opts: AttributeListFilterOpts
|
||||
): IFilter<AttributeFilterKeys> {
|
||||
return [
|
||||
{
|
||||
...createBooleanField(
|
||||
AttributeFilterKeys.availableInGrid,
|
||||
intl.formatMessage(messages.availableInGrid),
|
||||
opts.availableInGrid.value,
|
||||
{
|
||||
negative: intl.formatMessage(commonMessages.no),
|
||||
positive: intl.formatMessage(commonMessages.yes)
|
||||
}
|
||||
),
|
||||
active: opts.availableInGrid.active
|
||||
},
|
||||
{
|
||||
...createBooleanField(
|
||||
AttributeFilterKeys.filterableInDashboard,
|
||||
intl.formatMessage(messages.filterableInDashboard),
|
||||
opts.filterableInDashboard.value,
|
||||
{
|
||||
negative: intl.formatMessage(commonMessages.no),
|
||||
positive: intl.formatMessage(commonMessages.yes)
|
||||
}
|
||||
),
|
||||
active: opts.filterableInDashboard.active
|
||||
},
|
||||
{
|
||||
...createBooleanField(
|
||||
AttributeFilterKeys.filterableInStorefront,
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { Card, CardContent, TextField, Typography } from "@material-ui/core";
|
||||
import { ATTRIBUTE_TYPES_WITH_CONFIGURABLE_FACED_NAVIGATION } from "@saleor/attributes/utils/data";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import ControlledSwitch from "@saleor/components/ControlledSwitch";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import { AttributeErrorFragment, AttributeTypeEnum } from "@saleor/graphql";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors } from "@saleor/utils/errors";
|
||||
|
@ -39,7 +37,7 @@ const messages = defineMessages({
|
|||
description: "caption"
|
||||
},
|
||||
filterableInStorefront: {
|
||||
defaultMessage: "Use in Faceted Navigation",
|
||||
defaultMessage: "Use as filter",
|
||||
description: "attribute is filterable in storefront"
|
||||
},
|
||||
storefrontPropertiesTitle: {
|
||||
|
@ -77,10 +75,6 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
|||
|
||||
const formErrors = getFormErrors(["storefrontSearchPosition"], errors);
|
||||
|
||||
const dashboardProperties = ATTRIBUTE_TYPES_WITH_CONFIGURABLE_FACED_NAVIGATION.includes(
|
||||
data.inputType
|
||||
);
|
||||
|
||||
const storefrontFacetedNavigationProperties =
|
||||
ATTRIBUTE_TYPES_WITH_CONFIGURABLE_FACED_NAVIGATION.includes(
|
||||
data.inputType
|
||||
|
@ -90,41 +84,6 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
|||
<Card>
|
||||
<CardTitle title={intl.formatMessage(commonMessages.properties)} />
|
||||
<CardContent>
|
||||
{/* <Typography variant="subtitle1">
|
||||
<FormattedMessage
|
||||
defaultMessage="General Properties"
|
||||
description="attribute general properties section"
|
||||
|
||||
/>
|
||||
</Typography>
|
||||
<Hr />
|
||||
<CardSpacer />
|
||||
<ControlledSwitch
|
||||
name={"" as keyof AttributePageFormData}
|
||||
checked={false}
|
||||
disabled={disabled}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage
|
||||
defaultMessage="Variant Attribute"
|
||||
description="attribute is variant-only"
|
||||
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage
|
||||
defaultMessage="If enabled, you'll be able to use this attribute to create product variants"
|
||||
|
||||
/>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
onChange={onChange}
|
||||
/> */}
|
||||
|
||||
<Typography variant="subtitle1">
|
||||
<FormattedMessage {...messages.storefrontPropertiesTitle} />
|
||||
</Typography>
|
||||
<Hr />
|
||||
{storefrontFacetedNavigationProperties && (
|
||||
<>
|
||||
<ControlledCheckbox
|
||||
|
@ -154,9 +113,9 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
|||
/>
|
||||
</>
|
||||
)}
|
||||
<FormSpacer />
|
||||
</>
|
||||
)}
|
||||
<FormSpacer />
|
||||
<ControlledSwitch
|
||||
name={"visibleInStorefront" as keyof FormData}
|
||||
label={
|
||||
|
@ -171,47 +130,6 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
|||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{dashboardProperties && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
<Typography variant="subtitle1">
|
||||
<FormattedMessage {...messages.dashboardPropertiesTitle} />
|
||||
</Typography>
|
||||
<Hr />
|
||||
<CardSpacer />
|
||||
<ControlledCheckbox
|
||||
name={"filterableInDashboard" as keyof FormData}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage {...messages.filterableInDashboard} />
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage
|
||||
{...messages.filterableInDashboardCaption}
|
||||
/>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
checked={data.filterableInDashboard}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<ControlledCheckbox
|
||||
name={"availableInGrid" as keyof FormData}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage {...messages.availableInGrid} />
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage {...messages.availableInGridCaption} />
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
checked={data.availableInGrid}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -15,8 +15,6 @@ import {
|
|||
export const attributeSection = "/attributes/";
|
||||
|
||||
export enum AttributeListUrlFiltersEnum {
|
||||
availableInGrid = "availableInGrid",
|
||||
filterableInDashboard = "filterableInDashboard",
|
||||
filterableInStorefront = "filterableInStorefront",
|
||||
isVariantOnly = "isVariantOnly",
|
||||
valueRequired = "valueRequired",
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
import {
|
||||
AttributeEntityTypeEnum,
|
||||
AttributeErrorFragment,
|
||||
AttributeFragment,
|
||||
AttributeInputTypeEnum,
|
||||
AttributeValueDeleteMutation,
|
||||
AttributeValueFragment,
|
||||
|
@ -51,6 +52,14 @@ export const ATTRIBUTE_TYPES_WITH_CONFIGURABLE_FACED_NAVIGATION = [
|
|||
AttributeInputTypeEnum.SWATCH
|
||||
];
|
||||
|
||||
export function filterable(
|
||||
attribute: Pick<AttributeFragment, "inputType">
|
||||
): boolean {
|
||||
return ATTRIBUTE_TYPES_WITH_CONFIGURABLE_FACED_NAVIGATION.includes(
|
||||
attribute.inputType
|
||||
);
|
||||
}
|
||||
|
||||
export interface AttributeReference {
|
||||
label: string;
|
||||
value: string;
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
exports[`Filtering URL params should not be empty if active filters are present 1`] = `
|
||||
Object {
|
||||
"availableInGrid": "true",
|
||||
"filterableInDashboard": "true",
|
||||
"filterableInStorefront": "true",
|
||||
"isVariantOnly": "true",
|
||||
"valueRequired": "true",
|
||||
|
@ -11,4 +9,4 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"availableInGrid=true&filterableInDashboard=true&filterableInStorefront=true&isVariantOnly=true&valueRequired=true&visibleInStorefront=true"`;
|
||||
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"filterableInStorefront=true&isVariantOnly=true&valueRequired=true&visibleInStorefront=true"`;
|
||||
|
|
|
@ -18,7 +18,7 @@ describe("Filtering query params", () => {
|
|||
|
||||
it("should not be empty object if params given", () => {
|
||||
const params: AttributeListUrlFilters = {
|
||||
availableInGrid: true.toString()
|
||||
isVariantOnly: true.toString()
|
||||
};
|
||||
const filterVariables = getFilterVariables(params);
|
||||
|
||||
|
@ -30,14 +30,6 @@ describe("Filtering URL params", () => {
|
|||
const intl = createIntl(config);
|
||||
|
||||
const filters = createFilterStructure(intl, {
|
||||
availableInGrid: {
|
||||
active: false,
|
||||
value: true
|
||||
},
|
||||
filterableInDashboard: {
|
||||
active: false,
|
||||
value: true
|
||||
},
|
||||
filterableInStorefront: {
|
||||
active: false,
|
||||
value: true
|
||||
|
|
|
@ -23,14 +23,6 @@ export function getFilterOpts(
|
|||
params: AttributeListUrlFilters
|
||||
): AttributeListFilterOpts {
|
||||
return {
|
||||
availableInGrid: {
|
||||
active: params.availableInGrid !== undefined,
|
||||
value: maybe(() => parseBoolean(params.availableInGrid, true))
|
||||
},
|
||||
filterableInDashboard: {
|
||||
active: params.filterableInDashboard !== undefined,
|
||||
value: maybe(() => parseBoolean(params.filterableInDashboard, true))
|
||||
},
|
||||
filterableInStorefront: {
|
||||
active: params.filterableInStorefront !== undefined,
|
||||
value: maybe(() => parseBoolean(params.filterableInStorefront, true))
|
||||
|
@ -54,14 +46,6 @@ export function getFilterVariables(
|
|||
params: AttributeListUrlFilters
|
||||
): AttributeFilterInput {
|
||||
return {
|
||||
availableInGrid:
|
||||
params.availableInGrid !== undefined
|
||||
? parseBoolean(params.availableInGrid, false)
|
||||
: undefined,
|
||||
filterableInDashboard:
|
||||
params.filterableInDashboard !== undefined
|
||||
? parseBoolean(params.filterableInDashboard, false)
|
||||
: undefined,
|
||||
filterableInStorefront:
|
||||
params.filterableInStorefront !== undefined
|
||||
? parseBoolean(params.filterableInStorefront, false)
|
||||
|
@ -88,18 +72,6 @@ export function getFilterQueryParam(
|
|||
const { name } = filter;
|
||||
|
||||
switch (name) {
|
||||
case AttributeFilterKeys.availableInGrid:
|
||||
return getSingleValueQueryParam(
|
||||
filter,
|
||||
AttributeListUrlFiltersEnum.availableInGrid
|
||||
);
|
||||
|
||||
case AttributeFilterKeys.filterableInDashboard:
|
||||
return getSingleValueQueryParam(
|
||||
filter,
|
||||
AttributeListUrlFiltersEnum.filterableInDashboard
|
||||
);
|
||||
|
||||
case AttributeFilterKeys.filterableInStorefront:
|
||||
return getSingleValueQueryParam(
|
||||
filter,
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
import { ClickAwayListener, Grow, Popper } from "@material-ui/core";
|
||||
import { FormChange } from "@saleor/hooks/useForm";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { toggle } from "@saleor/utils/lists";
|
||||
import { Choice, ColumnsIcon, IconButton, makeStyles } from "@saleor/macaw-ui";
|
||||
import { FetchMoreProps } from "@saleor/types";
|
||||
import { score } from "fuzzaldrin";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import React from "react";
|
||||
|
||||
import ColumnPickerButton from "./ColumnPickerButton";
|
||||
import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField";
|
||||
import ColumnPickerContent, {
|
||||
ColumnPickerContentProps
|
||||
} from "./ColumnPickerContent";
|
||||
|
||||
export interface ColumnPickerProps
|
||||
extends Omit<
|
||||
ColumnPickerContentProps,
|
||||
"selectedColumns" | "onCancel" | "onColumnToggle" | "onReset" | "onSave"
|
||||
> {
|
||||
extends FetchMoreProps,
|
||||
Pick<ColumnPickerContentProps, "onQueryChange"> {
|
||||
className?: string;
|
||||
availableColumns: MultiAutocompleteChoiceType[];
|
||||
defaultColumns: string[];
|
||||
initialColumns: string[];
|
||||
initialColumns: Choice[];
|
||||
initialOpen?: boolean;
|
||||
query: string;
|
||||
onSave: (columns: string[]) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
popper: {
|
||||
marginTop: theme.spacing(1),
|
||||
zIndex: 2
|
||||
marginTop: theme.spacing(1)
|
||||
}
|
||||
}),
|
||||
{
|
||||
|
@ -36,54 +38,77 @@ const useStyles = makeStyles(
|
|||
const ColumnPicker: React.FC<ColumnPickerProps> = props => {
|
||||
const {
|
||||
className,
|
||||
columns,
|
||||
availableColumns,
|
||||
defaultColumns,
|
||||
hasMore,
|
||||
initialColumns,
|
||||
initialOpen = false,
|
||||
total,
|
||||
onFetchMore,
|
||||
onSave
|
||||
onSave,
|
||||
query,
|
||||
...rest
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
const selectedColumns = React.useRef(
|
||||
initialColumns.map(({ value }) => value)
|
||||
);
|
||||
const [isExpanded, setExpansionState] = React.useState(false);
|
||||
const [selectedColumns, setSelectedColumns] = useStateFromProps(
|
||||
|
||||
// Component is uncontrolled but we need to reset it somehow, so we change
|
||||
// initial prop after reset callback to force value refreshing
|
||||
const [initialColumnsChoices, setInitialColumnsChoices] = useStateFromProps(
|
||||
initialColumns
|
||||
);
|
||||
|
||||
const onChange: FormChange<string[]> = event => {
|
||||
selectedColumns.current = event.target.value;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => setExpansionState(initialOpen), 100);
|
||||
}, []);
|
||||
|
||||
const handleCancel = () => {
|
||||
setExpansionState(false);
|
||||
setSelectedColumns(initialColumns);
|
||||
selectedColumns.current = initialColumns.map(({ value }) => value);
|
||||
};
|
||||
|
||||
const handleColumnToggle = (column: string) =>
|
||||
setSelectedColumns(toggle(column, selectedColumns, (a, b) => a === b));
|
||||
|
||||
const handleReset = () => setSelectedColumns(defaultColumns);
|
||||
const handleReset = () => {
|
||||
selectedColumns.current = defaultColumns;
|
||||
const defaultColumnsChoices = defaultColumns.map(value => ({
|
||||
label: availableColumns.find(column => column.value === value)?.label,
|
||||
value
|
||||
}));
|
||||
setInitialColumnsChoices(defaultColumnsChoices);
|
||||
onChange({ target: { name: "", value: defaultColumns } });
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
setExpansionState(false);
|
||||
onSave(selectedColumns);
|
||||
onSave(selectedColumns.current);
|
||||
};
|
||||
|
||||
const choices = sortBy(
|
||||
availableColumns.map(column => ({
|
||||
...column,
|
||||
score: -score(column.label, query)
|
||||
})),
|
||||
"score"
|
||||
);
|
||||
|
||||
return (
|
||||
<ClickAwayListener onClickAway={() => setExpansionState(false)}>
|
||||
<div ref={anchor} className={className}>
|
||||
<ColumnPickerButton
|
||||
active={isExpanded}
|
||||
<IconButton
|
||||
state={isExpanded ? "active" : "default"}
|
||||
onClick={() => setExpansionState(prevState => !prevState)}
|
||||
/>
|
||||
>
|
||||
<ColumnsIcon />
|
||||
</IconButton>
|
||||
<Popper
|
||||
className={classes.popper}
|
||||
open={isExpanded}
|
||||
anchorEl={anchor.current}
|
||||
transition
|
||||
disablePortal
|
||||
placement="bottom-end"
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
|
@ -95,15 +120,13 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
|
|||
}}
|
||||
>
|
||||
<ColumnPickerContent
|
||||
columns={columns}
|
||||
hasMore={hasMore}
|
||||
selectedColumns={selectedColumns}
|
||||
total={total}
|
||||
choices={choices}
|
||||
initialValues={initialColumnsChoices}
|
||||
onCancel={handleCancel}
|
||||
onColumnToggle={handleColumnToggle}
|
||||
onFetchMore={onFetchMore}
|
||||
onChange={onChange}
|
||||
onReset={handleReset}
|
||||
onSave={handleSave}
|
||||
{...rest}
|
||||
/>
|
||||
</Grow>
|
||||
)}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
interface ColumnPickerButtonProps {
|
||||
active: boolean;
|
||||
className?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
icon: {
|
||||
marginLeft: theme.spacing(2),
|
||||
transition: theme.transitions.duration.short + "ms"
|
||||
},
|
||||
root: {
|
||||
"& span": {
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
paddingRight: theme.spacing(1)
|
||||
},
|
||||
rootActive: {
|
||||
background: fade(theme.palette.primary.main, 0.1)
|
||||
},
|
||||
rotate: {
|
||||
transform: "rotate(180deg)"
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "ColumnPickerButton"
|
||||
}
|
||||
);
|
||||
|
||||
const ColumnPickerButton: React.FC<ColumnPickerButtonProps> = props => {
|
||||
const { active, className, onClick } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={classNames(classes.root, className, {
|
||||
[classes.rootActive]: active
|
||||
})}
|
||||
onClick={onClick}
|
||||
variant="secondary"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Columns"
|
||||
description="select visible columns button"
|
||||
/>
|
||||
<ArrowDropDownIcon
|
||||
className={classNames(classes.icon, {
|
||||
[classes.rotate]: active
|
||||
})}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColumnPickerButton;
|
|
@ -1,194 +1,123 @@
|
|||
import {
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CircularProgress,
|
||||
CardHeader,
|
||||
MenuItem,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import useElementScroll from "@saleor/hooks/useElementScroll";
|
||||
import { FormChange } from "@saleor/hooks/useForm";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
import {
|
||||
Button,
|
||||
Choice,
|
||||
CloseIcon,
|
||||
IconButton,
|
||||
makeStyles,
|
||||
MultipleValueAutocomplete
|
||||
} from "@saleor/macaw-ui";
|
||||
import { FetchMoreProps } from "@saleor/types";
|
||||
import { isSelected } from "@saleor/utils/lists";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import InfiniteScroll from "react-infinite-scroll-component";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ControlledCheckbox from "../ControlledCheckbox";
|
||||
import Hr from "../Hr";
|
||||
import messages from "./messages";
|
||||
|
||||
export interface ColumnPickerChoice {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
export interface ColumnPickerContentProps extends Partial<FetchMoreProps> {
|
||||
columns: ColumnPickerChoice[];
|
||||
selectedColumns: string[];
|
||||
total?: number;
|
||||
export interface ColumnPickerContentProps extends FetchMoreProps {
|
||||
choices: Choice[];
|
||||
initialValues: Choice[];
|
||||
onCancel: () => void;
|
||||
onColumnToggle: (column: string) => void;
|
||||
onChange: FormChange<string[]>;
|
||||
onReset: () => void;
|
||||
onSave: () => void;
|
||||
onQueryChange: (query: string) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
actionBar: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between"
|
||||
},
|
||||
actionBarContainer: {
|
||||
"&&": {
|
||||
padding: theme.spacing(2)
|
||||
},
|
||||
boxShadow: `0px 0px 0px 0px ${theme.palette.background.paper}`,
|
||||
transition: theme.transitions.duration.short + "ms"
|
||||
},
|
||||
cancelButton: {
|
||||
marginRight: theme.spacing(2)
|
||||
},
|
||||
dialogPaper: {
|
||||
overflow: "hidden"
|
||||
actions: {
|
||||
flexDirection: "row-reverse",
|
||||
gap: theme.spacing(1),
|
||||
paddingBottom: theme.spacing(2)
|
||||
},
|
||||
content: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
gridTemplateColumns: "repeat(2, 1fr)"
|
||||
},
|
||||
display: "grid",
|
||||
gridColumnGap: theme.spacing(3),
|
||||
gridTemplateColumns: "repeat(3, 210px)",
|
||||
padding: theme.spacing(2, 3)
|
||||
paddingBottom: theme.spacing(2),
|
||||
width: 450
|
||||
},
|
||||
contentContainer: {
|
||||
maxHeight: 256,
|
||||
overflowX: "visible",
|
||||
overflowY: "scroll",
|
||||
padding: 0
|
||||
},
|
||||
dropShadow: {
|
||||
boxShadow: `0px -5px 10px 0px ${theme.palette.divider}`
|
||||
},
|
||||
label: {
|
||||
"& span": {
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis"
|
||||
},
|
||||
marginRight: 0
|
||||
},
|
||||
loadMoreLoaderContainer: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
gridColumnEnd: "span 3",
|
||||
height: theme.spacing(3),
|
||||
justifyContent: "center"
|
||||
},
|
||||
titleContainer: {
|
||||
padding: theme.spacing(1.5, 3.5)
|
||||
subHeader: {
|
||||
fontWeight: 500,
|
||||
letterSpacing: "0.1rem",
|
||||
textTransform: "uppercase",
|
||||
marginBottom: theme.spacing(1)
|
||||
}
|
||||
}),
|
||||
{ name: "ColumnPickerContent" }
|
||||
);
|
||||
|
||||
const scrollableTargetId = "columnPickerScrollableDiv";
|
||||
|
||||
const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
||||
const {
|
||||
columns,
|
||||
hasMore,
|
||||
selectedColumns,
|
||||
total,
|
||||
choices,
|
||||
initialValues,
|
||||
loading,
|
||||
onCancel,
|
||||
onColumnToggle,
|
||||
onFetchMore,
|
||||
onChange,
|
||||
onReset,
|
||||
onSave
|
||||
onFetchMore,
|
||||
onSave,
|
||||
onQueryChange
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
const scrollPosition = useElementScroll(anchor);
|
||||
|
||||
const dropShadow =
|
||||
anchor.current && scrollPosition
|
||||
? scrollPosition.y + anchor.current.clientHeight <
|
||||
anchor.current.scrollHeight
|
||||
: false;
|
||||
const classes = useStyles();
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card elevation={8}>
|
||||
<CardContent className={classes.titleContainer}>
|
||||
<Typography color="textSecondary">
|
||||
<FormattedMessage
|
||||
defaultMessage="{numberOfSelected} columns selected out of {numberOfTotal}"
|
||||
description="pick columns to display"
|
||||
values={{
|
||||
numberOfSelected: selectedColumns.length,
|
||||
numberOfTotal: total || columns.length
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<Hr />
|
||||
<CardContent
|
||||
className={classes.contentContainer}
|
||||
ref={anchor}
|
||||
id={scrollableTargetId}
|
||||
>
|
||||
<InfiniteScroll
|
||||
dataLength={columns.length}
|
||||
next={onFetchMore}
|
||||
hasMore={hasMore}
|
||||
scrollThreshold="100px"
|
||||
loader={
|
||||
<div className={classes.loadMoreLoaderContainer}>
|
||||
<CircularProgress size={16} />
|
||||
</div>
|
||||
}
|
||||
scrollableTarget={scrollableTargetId}
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton variant="secondary" onClick={onCancel}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
/>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography
|
||||
color="textSecondary"
|
||||
variant="caption"
|
||||
className={classes.subHeader}
|
||||
>
|
||||
<div className={classes.content}>
|
||||
{columns.map(column => (
|
||||
<ControlledCheckbox
|
||||
className={classes.label}
|
||||
checked={isSelected(
|
||||
column.value,
|
||||
selectedColumns,
|
||||
(a, b) => a === b
|
||||
)}
|
||||
name={column.value}
|
||||
label={column.label}
|
||||
onChange={() => onColumnToggle(column.value)}
|
||||
key={column.value}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
</CardContent>
|
||||
<Hr />
|
||||
<CardContent
|
||||
className={classNames(classes.actionBarContainer, {
|
||||
[classes.dropShadow]: dropShadow
|
||||
})}
|
||||
>
|
||||
<div className={classes.actionBar}>
|
||||
<Button variant="secondary" color="text" onClick={onReset}>
|
||||
<FormattedMessage defaultMessage="Reset" description="button" />
|
||||
</Button>
|
||||
<div>
|
||||
<Button
|
||||
className={classes.cancelButton}
|
||||
color="text"
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<Button variant="primary" onClick={onSave}>
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{intl.formatMessage(messages.columnSubheader)}
|
||||
</Typography>
|
||||
<MultipleValueAutocomplete
|
||||
choices={choices}
|
||||
enableReinitialize
|
||||
fullWidth
|
||||
label={intl.formatMessage(messages.columnLabel)}
|
||||
loading={loading}
|
||||
name="columns"
|
||||
initialValue={initialValues}
|
||||
onChange={onChange}
|
||||
onInputChange={onQueryChange}
|
||||
onScrollToBottom={onFetchMore}
|
||||
>
|
||||
{({ choices, getItemProps }) =>
|
||||
choices.map((choice, choiceIndex) => (
|
||||
<MenuItem
|
||||
key={choice.value}
|
||||
{...getItemProps({ item: choice, index: choiceIndex })}
|
||||
>
|
||||
{choice.label}
|
||||
</MenuItem>
|
||||
))
|
||||
}
|
||||
</MultipleValueAutocomplete>
|
||||
</CardContent>
|
||||
<CardActions className={classes.actions}>
|
||||
<Button variant="primary" onClick={onSave}>
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</Button>
|
||||
<Button color="text" variant="secondary" onClick={onReset}>
|
||||
<FormattedMessage {...buttonMessages.reset} />
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export { default } from "./ColumnPicker";
|
||||
export * from "./ColumnPicker";
|
||||
export * from "./ColumnPickerButton";
|
||||
export * from "./ColumnPickerContent";
|
||||
|
|
18
src/components/ColumnPicker/messages.ts
Normal file
18
src/components/ColumnPicker/messages.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
defaultMessage: "Customize list",
|
||||
description: "header"
|
||||
},
|
||||
columnSubheader: {
|
||||
defaultMessage: "Column settings",
|
||||
description: "header"
|
||||
},
|
||||
columnLabel: {
|
||||
defaultMessage: "Selected columns",
|
||||
description: "input label"
|
||||
}
|
||||
});
|
||||
|
||||
export default messages;
|
|
@ -1,4 +1,9 @@
|
|||
import { Popper, TextField, Typography } from "@material-ui/core";
|
||||
import {
|
||||
Popper,
|
||||
PopperPlacementType,
|
||||
TextField,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import CloseIcon from "@material-ui/icons/Close";
|
||||
import Debounce, { DebounceProps } from "@saleor/components/Debounce";
|
||||
|
@ -103,6 +108,7 @@ export interface MultiAutocompleteSelectFieldProps
|
|||
onBlur?: () => void;
|
||||
fetchOnFocus?: boolean;
|
||||
endAdornment?: React.ReactNode;
|
||||
popperPlacement?: PopperPlacementType;
|
||||
}
|
||||
|
||||
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
||||
|
@ -131,6 +137,7 @@ const MultiAutocompleteSelectFieldComponent: React.FC<MultiAutocompleteSelectFie
|
|||
onFetchMore,
|
||||
fetchOnFocus,
|
||||
endAdornment,
|
||||
popperPlacement = "bottom-end",
|
||||
...rest
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
@ -229,7 +236,7 @@ const MultiAutocompleteSelectFieldComponent: React.FC<MultiAutocompleteSelectFie
|
|||
width: anchor.current.clientWidth,
|
||||
zIndex: 1301
|
||||
}}
|
||||
placement="bottom-end"
|
||||
placement={popperPlacement}
|
||||
>
|
||||
<MultiAutocompleteSelectFieldContent
|
||||
add={
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { InputBase, Popper, TextField } from "@material-ui/core";
|
||||
import {
|
||||
InputBase,
|
||||
Popper,
|
||||
PopperPlacementType,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import { InputProps } from "@material-ui/core/Input";
|
||||
import { ExtendedFormHelperTextProps } from "@saleor/channels/components/ChannelForm/types";
|
||||
import { ChevronIcon, makeStyles } from "@saleor/macaw-ui";
|
||||
|
@ -62,6 +67,7 @@ export interface SingleAutocompleteSelectFieldProps
|
|||
FormHelperTextProps?: ExtendedFormHelperTextProps;
|
||||
nakedInput?: boolean;
|
||||
onBlur?: () => void;
|
||||
popperPlacement?: PopperPlacementType;
|
||||
}
|
||||
|
||||
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
||||
|
@ -93,6 +99,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
|||
FormHelperTextProps,
|
||||
nakedInput = false,
|
||||
onBlur,
|
||||
popperPlacement = "bottom-end",
|
||||
...rest
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
@ -250,7 +257,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
|||
anchorEl={anchor.current}
|
||||
open={isOpen}
|
||||
style={{ width: anchor.current.clientWidth, zIndex: 1301 }}
|
||||
placement="bottom-end"
|
||||
placement={popperPlacement}
|
||||
>
|
||||
<SingleAutocompleteSelectFieldContent
|
||||
add={
|
||||
|
|
|
@ -357,3 +357,14 @@ export const exportFileFragment = gql`
|
|||
url
|
||||
}
|
||||
`;
|
||||
|
||||
export const productListAttribute = gql`
|
||||
fragment ProductListAttribute on SelectedAttribute {
|
||||
attribute {
|
||||
id
|
||||
}
|
||||
values {
|
||||
...AttributeValue
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -2043,6 +2043,16 @@ export const ExportFileFragmentDoc = gql`
|
|||
url
|
||||
}
|
||||
`;
|
||||
export const ProductListAttributeFragmentDoc = gql`
|
||||
fragment ProductListAttribute on SelectedAttribute {
|
||||
attribute {
|
||||
id
|
||||
}
|
||||
values {
|
||||
...AttributeValue
|
||||
}
|
||||
}
|
||||
${AttributeValueFragmentDoc}`;
|
||||
export const ShippingMethodWithPostalCodesFragmentDoc = gql`
|
||||
fragment ShippingMethodWithPostalCodes on ShippingMethodType {
|
||||
id
|
||||
|
@ -11931,10 +11941,7 @@ export type ProductVariantPreorderDeactivateMutationResult = Apollo.MutationResu
|
|||
export type ProductVariantPreorderDeactivateMutationOptions = Apollo.BaseMutationOptions<Types.ProductVariantPreorderDeactivateMutation, Types.ProductVariantPreorderDeactivateMutationVariables>;
|
||||
export const InitialProductFilterAttributesDocument = gql`
|
||||
query InitialProductFilterAttributes {
|
||||
attributes(
|
||||
first: 100
|
||||
filter: {filterableInDashboard: true, type: PRODUCT_TYPE}
|
||||
) {
|
||||
attributes(first: 100, filter: {type: PRODUCT_TYPE}) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
|
@ -12109,12 +12116,7 @@ export const ProductListDocument = gql`
|
|||
...ProductWithChannelListings
|
||||
updatedAt
|
||||
attributes @include(if: $hasSelectedAttributes) {
|
||||
attribute {
|
||||
id
|
||||
}
|
||||
values {
|
||||
...AttributeValue
|
||||
}
|
||||
...ProductListAttribute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12128,7 +12130,7 @@ export const ProductListDocument = gql`
|
|||
}
|
||||
}
|
||||
${ProductWithChannelListingsFragmentDoc}
|
||||
${AttributeValueFragmentDoc}`;
|
||||
${ProductListAttributeFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useProductListQuery__
|
||||
|
@ -12475,55 +12477,6 @@ export function useProductMediaByIdLazyQuery(baseOptions?: ApolloReactHooks.Lazy
|
|||
export type ProductMediaByIdQueryHookResult = ReturnType<typeof useProductMediaByIdQuery>;
|
||||
export type ProductMediaByIdLazyQueryHookResult = ReturnType<typeof useProductMediaByIdLazyQuery>;
|
||||
export type ProductMediaByIdQueryResult = Apollo.QueryResult<Types.ProductMediaByIdQuery, Types.ProductMediaByIdQueryVariables>;
|
||||
export const AvailableInGridAttributesDocument = gql`
|
||||
query AvailableInGridAttributes($first: Int!, $after: String) {
|
||||
availableInGrid: attributes(
|
||||
first: $first
|
||||
after: $after
|
||||
filter: {availableInGrid: true, isVariantOnly: false, type: PRODUCT_TYPE}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
${PageInfoFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useAvailableInGridAttributesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useAvailableInGridAttributesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useAvailableInGridAttributesQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useAvailableInGridAttributesQuery({
|
||||
* variables: {
|
||||
* first: // value for 'first'
|
||||
* after: // value for 'after'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useAvailableInGridAttributesQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.AvailableInGridAttributesQuery, Types.AvailableInGridAttributesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return ApolloReactHooks.useQuery<Types.AvailableInGridAttributesQuery, Types.AvailableInGridAttributesQueryVariables>(AvailableInGridAttributesDocument, options);
|
||||
}
|
||||
export function useAvailableInGridAttributesLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.AvailableInGridAttributesQuery, Types.AvailableInGridAttributesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return ApolloReactHooks.useLazyQuery<Types.AvailableInGridAttributesQuery, Types.AvailableInGridAttributesQueryVariables>(AvailableInGridAttributesDocument, options);
|
||||
}
|
||||
export type AvailableInGridAttributesQueryHookResult = ReturnType<typeof useAvailableInGridAttributesQuery>;
|
||||
export type AvailableInGridAttributesLazyQueryHookResult = ReturnType<typeof useAvailableInGridAttributesLazyQuery>;
|
||||
export type AvailableInGridAttributesQueryResult = Apollo.QueryResult<Types.AvailableInGridAttributesQuery, Types.AvailableInGridAttributesQueryVariables>;
|
||||
export const GridAttributesDocument = gql`
|
||||
query GridAttributes($ids: [ID!]!) {
|
||||
grid: attributes(first: 25, filter: {ids: $ids}) {
|
||||
|
@ -12705,6 +12658,56 @@ export function useSearchAttributeValuesLazyQuery(baseOptions?: ApolloReactHooks
|
|||
export type SearchAttributeValuesQueryHookResult = ReturnType<typeof useSearchAttributeValuesQuery>;
|
||||
export type SearchAttributeValuesLazyQueryHookResult = ReturnType<typeof useSearchAttributeValuesLazyQuery>;
|
||||
export type SearchAttributeValuesQueryResult = Apollo.QueryResult<Types.SearchAttributeValuesQuery, Types.SearchAttributeValuesQueryVariables>;
|
||||
export const SearchAvailableInGridAttributesDocument = gql`
|
||||
query SearchAvailableInGridAttributes($first: Int!, $after: String, $query: String!) {
|
||||
availableInGrid: attributes(
|
||||
first: $first
|
||||
after: $after
|
||||
filter: {isVariantOnly: false, type: PRODUCT_TYPE, search: $query}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
${PageInfoFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useSearchAvailableInGridAttributesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useSearchAvailableInGridAttributesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useSearchAvailableInGridAttributesQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useSearchAvailableInGridAttributesQuery({
|
||||
* variables: {
|
||||
* first: // value for 'first'
|
||||
* after: // value for 'after'
|
||||
* query: // value for 'query'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useSearchAvailableInGridAttributesQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.SearchAvailableInGridAttributesQuery, Types.SearchAvailableInGridAttributesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return ApolloReactHooks.useQuery<Types.SearchAvailableInGridAttributesQuery, Types.SearchAvailableInGridAttributesQueryVariables>(SearchAvailableInGridAttributesDocument, options);
|
||||
}
|
||||
export function useSearchAvailableInGridAttributesLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.SearchAvailableInGridAttributesQuery, Types.SearchAvailableInGridAttributesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return ApolloReactHooks.useLazyQuery<Types.SearchAvailableInGridAttributesQuery, Types.SearchAvailableInGridAttributesQueryVariables>(SearchAvailableInGridAttributesDocument, options);
|
||||
}
|
||||
export type SearchAvailableInGridAttributesQueryHookResult = ReturnType<typeof useSearchAvailableInGridAttributesQuery>;
|
||||
export type SearchAvailableInGridAttributesLazyQueryHookResult = ReturnType<typeof useSearchAvailableInGridAttributesLazyQuery>;
|
||||
export type SearchAvailableInGridAttributesQueryResult = Apollo.QueryResult<Types.SearchAvailableInGridAttributesQuery, Types.SearchAvailableInGridAttributesQueryVariables>;
|
||||
export const SearchAvailablePageAttributesDocument = gql`
|
||||
query SearchAvailablePageAttributes($id: ID!, $after: String, $first: Int!, $query: String!) {
|
||||
pageType(id: $id) {
|
||||
|
|
|
@ -520,7 +520,11 @@ export type CatalogueInput = {
|
|||
categories?: InputMaybe<Array<Scalars['ID']>>;
|
||||
/** Collections related to the discount. */
|
||||
collections?: InputMaybe<Array<Scalars['ID']>>;
|
||||
/** Added in Saleor 3.1. Product variant related to the discount. */
|
||||
/**
|
||||
* Product variant related to the discount.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
variants?: InputMaybe<Array<Scalars['ID']>>;
|
||||
};
|
||||
|
||||
|
@ -576,7 +580,11 @@ export type ChannelCreateInput = {
|
|||
slug: Scalars['String'];
|
||||
/** Currency of the channel. */
|
||||
currencyCode: Scalars['String'];
|
||||
/** Added in Saleor 3.1. Default country for the channel. Default country can be used in checkout to determine the stock quantities or calculate taxes when the country was not explicitly provided. */
|
||||
/**
|
||||
* Default country for the channel. Default country can be used in checkout to determine the stock quantities or calculate taxes when the country was not explicitly provided.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
defaultCountry: CountryCode;
|
||||
/** List of shipping zones to assign to the channel. */
|
||||
addShippingZones?: InputMaybe<Array<Scalars['ID']>>;
|
||||
|
@ -607,7 +615,11 @@ export type ChannelUpdateInput = {
|
|||
name?: InputMaybe<Scalars['String']>;
|
||||
/** Slug of the channel. */
|
||||
slug?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.1. Default country for the channel. Default country can be used in checkout to determine the stock quantities or calculate taxes when the country was not explicitly provided. */
|
||||
/**
|
||||
* Default country for the channel. Default country can be used in checkout to determine the stock quantities or calculate taxes when the country was not explicitly provided.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
defaultCountry?: InputMaybe<CountryCode>;
|
||||
/** List of shipping zones to assign to the channel. */
|
||||
addShippingZones?: InputMaybe<Array<Scalars['ID']>>;
|
||||
|
@ -674,7 +686,13 @@ export type CheckoutLineInput = {
|
|||
quantity: Scalars['Int'];
|
||||
/** ID of the product variant. */
|
||||
variantId: Scalars['ID'];
|
||||
/** Added in Saleor 3.1. Custom price of the item. Can be set only by apps with `HANDLE_CHECKOUTS` permission. When the line with the same variant will be provided multiple times, the last price will be used. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Custom price of the item. Can be set only by apps with `HANDLE_CHECKOUTS` permission. When the line with the same variant will be provided multiple times, the last price will be used.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
price?: InputMaybe<Scalars['PositiveDecimal']>;
|
||||
};
|
||||
|
||||
|
@ -683,7 +701,13 @@ export type CheckoutLineUpdateInput = {
|
|||
quantity?: InputMaybe<Scalars['Int']>;
|
||||
/** ID of the product variant. */
|
||||
variantId: Scalars['ID'];
|
||||
/** Added in Saleor 3.1. Custom price of the item. Can be set only by apps with `HANDLE_CHECKOUTS` permission. When the line with the same variant will be provided multiple times, the last price will be used. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Custom price of the item. Can be set only by apps with `HANDLE_CHECKOUTS` permission. When the line with the same variant will be provided multiple times, the last price will be used.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
price?: InputMaybe<Scalars['PositiveDecimal']>;
|
||||
};
|
||||
|
||||
|
@ -1451,9 +1475,21 @@ export type GiftCardBulkCreateInput = {
|
|||
};
|
||||
|
||||
export type GiftCardCreateInput = {
|
||||
/** Added in Saleor 3.1. The gift card tags to add. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* The gift card tags to add.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
addTags?: InputMaybe<Array<Scalars['String']>>;
|
||||
/** Added in Saleor 3.1. The gift card expiry date. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* The gift card expiry date.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
expiryDate?: InputMaybe<Scalars['Date']>;
|
||||
/**
|
||||
* Start date of the gift card in ISO 8601 format.
|
||||
|
@ -1471,9 +1507,21 @@ export type GiftCardCreateInput = {
|
|||
balance: PriceInput;
|
||||
/** Email of the customer to whom gift card will be sent. */
|
||||
userEmail?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.1. Slug of a channel from which the email should be sent. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Slug of a channel from which the email should be sent.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
channel?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.1. Determine if gift card is active. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Determine if gift card is active.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
isActive: Scalars['Boolean'];
|
||||
/**
|
||||
* Code to use the gift card.
|
||||
|
@ -1481,7 +1529,13 @@ export type GiftCardCreateInput = {
|
|||
* DEPRECATED: this field will be removed in Saleor 4.0. The code is now auto generated.
|
||||
*/
|
||||
code?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.1. The gift card note from the staff member. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* The gift card note from the staff member.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
note?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
@ -1581,9 +1635,21 @@ export type GiftCardTagFilterInput = {
|
|||
};
|
||||
|
||||
export type GiftCardUpdateInput = {
|
||||
/** Added in Saleor 3.1. The gift card tags to add. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* The gift card tags to add.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
addTags?: InputMaybe<Array<Scalars['String']>>;
|
||||
/** Added in Saleor 3.1. The gift card expiry date. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* The gift card expiry date.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
expiryDate?: InputMaybe<Scalars['Date']>;
|
||||
/**
|
||||
* Start date of the gift card in ISO 8601 format.
|
||||
|
@ -1597,9 +1663,21 @@ export type GiftCardUpdateInput = {
|
|||
* DEPRECATED: this field will be removed in Saleor 4.0. Use `expiryDate` from `expirySettings` instead.
|
||||
*/
|
||||
endDate?: InputMaybe<Scalars['Date']>;
|
||||
/** Added in Saleor 3.1. The gift card tags to remove. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* The gift card tags to remove.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
removeTags?: InputMaybe<Array<Scalars['String']>>;
|
||||
/** Added in Saleor 3.1. The gift card balance amount. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* The gift card balance amount.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
balanceAmount?: InputMaybe<Scalars['PositiveDecimal']>;
|
||||
};
|
||||
|
||||
|
@ -2976,7 +3054,11 @@ export type PageCreateInput = {
|
|||
* DEPRECATED: this field will be removed in Saleor 4.0. Use `publishedAt` field instead.
|
||||
*/
|
||||
publicationDate?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.3. Publication date time. ISO 8601 standard. */
|
||||
/**
|
||||
* Publication date time. ISO 8601 standard.
|
||||
*
|
||||
* Added in Saleor 3.3.
|
||||
*/
|
||||
publishedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
/** Search engine optimization fields. */
|
||||
seo?: InputMaybe<SeoInput>;
|
||||
|
@ -3019,7 +3101,11 @@ export type PageInput = {
|
|||
* DEPRECATED: this field will be removed in Saleor 4.0. Use `publishedAt` field instead.
|
||||
*/
|
||||
publicationDate?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.3. Publication date time. ISO 8601 standard. */
|
||||
/**
|
||||
* Publication date time. ISO 8601 standard.
|
||||
*
|
||||
* Added in Saleor 3.3.
|
||||
*/
|
||||
publishedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
/** Search engine optimization fields. */
|
||||
seo?: InputMaybe<SeoInput>;
|
||||
|
@ -3157,9 +3243,17 @@ export type PaymentInput = {
|
|||
amount?: InputMaybe<Scalars['PositiveDecimal']>;
|
||||
/** URL of a storefront view where user should be redirected after requiring additional actions. Payment with additional actions will not be finished if this field is not provided. */
|
||||
returnUrl?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.1. Payment store type. */
|
||||
/**
|
||||
* Payment store type.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
storePaymentMethod?: InputMaybe<StorePaymentMethodEnum>;
|
||||
/** Added in Saleor 3.1. User public metadata. */
|
||||
/**
|
||||
* User public metadata.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
metadata?: InputMaybe<Array<MetadataInput>>;
|
||||
};
|
||||
|
||||
|
@ -3315,14 +3409,22 @@ export type ProductAttributeAssignInput = {
|
|||
id: Scalars['ID'];
|
||||
/** The attribute type to be assigned as. */
|
||||
type: ProductAttributeType;
|
||||
/** Added in Saleor 3.1. Whether attribute is allowed in variant selection. Allowed types are: ['dropdown', 'boolean', 'swatch', 'numeric']. */
|
||||
/**
|
||||
* Whether attribute is allowed in variant selection. Allowed types are: ['dropdown', 'boolean', 'swatch', 'numeric'].
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
variantSelection?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type ProductAttributeAssignmentUpdateInput = {
|
||||
/** The ID of the attribute to assign. */
|
||||
id: Scalars['ID'];
|
||||
/** Added in Saleor 3.1. Whether attribute is allowed in variant selection. Allowed types are: ['dropdown', 'boolean', 'swatch', 'numeric']. */
|
||||
/**
|
||||
* Whether attribute is allowed in variant selection. Allowed types are: ['dropdown', 'boolean', 'swatch', 'numeric'].
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
variantSelection: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
|
@ -3342,7 +3444,11 @@ export type ProductChannelListingAddInput = {
|
|||
* DEPRECATED: this field will be removed in Saleor 4.0. Use `publishedAt` field instead.
|
||||
*/
|
||||
publicationDate?: InputMaybe<Scalars['Date']>;
|
||||
/** Added in Saleor 3.3. Publication date time. ISO 8601 standard. */
|
||||
/**
|
||||
* Publication date time. ISO 8601 standard.
|
||||
*
|
||||
* Added in Saleor 3.3.
|
||||
*/
|
||||
publishedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
/** Determines if product is visible in product listings (doesn't apply to product collections). */
|
||||
visibleInListings?: InputMaybe<Scalars['Boolean']>;
|
||||
|
@ -3354,7 +3460,11 @@ export type ProductChannelListingAddInput = {
|
|||
* DEPRECATED: this field will be removed in Saleor 4.0. Use `availableForPurchaseAt` field instead.
|
||||
*/
|
||||
availableForPurchaseDate?: InputMaybe<Scalars['Date']>;
|
||||
/** Added in Saleor 3.3. A start date time from which a product will be available for purchase. When not set and `isAvailable` is set to True, the current day is assumed. */
|
||||
/**
|
||||
* A start date time from which a product will be available for purchase. When not set and `isAvailable` is set to True, the current day is assumed.
|
||||
*
|
||||
* Added in Saleor 3.3.
|
||||
*/
|
||||
availableForPurchaseAt?: InputMaybe<Scalars['DateTime']>;
|
||||
/** List of variants to which the channel should be assigned. */
|
||||
addVariants?: InputMaybe<Array<Scalars['ID']>>;
|
||||
|
@ -3631,9 +3741,21 @@ export type ProductVariantBulkCreateInput = {
|
|||
trackInventory?: InputMaybe<Scalars['Boolean']>;
|
||||
/** Weight of the Product Variant. */
|
||||
weight?: InputMaybe<Scalars['WeightScalar']>;
|
||||
/** Added in Saleor 3.1. Determines if variant is in preorder. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Determines if variant is in preorder.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
preorder?: InputMaybe<PreorderSettingsInput>;
|
||||
/** Added in Saleor 3.1. Determines maximum quantity of `ProductVariant`,that can be bought in a single checkout. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Determines maximum quantity of `ProductVariant`,that can be bought in a single checkout.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
quantityLimitPerCustomer?: InputMaybe<Scalars['Int']>;
|
||||
/** Stocks of a product available for sale. */
|
||||
stocks?: InputMaybe<Array<StockInput>>;
|
||||
|
@ -3648,7 +3770,13 @@ export type ProductVariantChannelListingAddInput = {
|
|||
price: Scalars['PositiveDecimal'];
|
||||
/** Cost price of the variant in channel. */
|
||||
costPrice?: InputMaybe<Scalars['PositiveDecimal']>;
|
||||
/** Added in Saleor 3.1. The threshold for preorder variant in channel. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* The threshold for preorder variant in channel.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
preorderThreshold?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
@ -3661,9 +3789,21 @@ export type ProductVariantCreateInput = {
|
|||
trackInventory?: InputMaybe<Scalars['Boolean']>;
|
||||
/** Weight of the Product Variant. */
|
||||
weight?: InputMaybe<Scalars['WeightScalar']>;
|
||||
/** Added in Saleor 3.1. Determines if variant is in preorder. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Determines if variant is in preorder.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
preorder?: InputMaybe<PreorderSettingsInput>;
|
||||
/** Added in Saleor 3.1. Determines maximum quantity of `ProductVariant`,that can be bought in a single checkout. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Determines maximum quantity of `ProductVariant`,that can be bought in a single checkout.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
quantityLimitPerCustomer?: InputMaybe<Scalars['Int']>;
|
||||
/** Product ID of which type is the variant. */
|
||||
product: Scalars['ID'];
|
||||
|
@ -3688,9 +3828,21 @@ export type ProductVariantInput = {
|
|||
trackInventory?: InputMaybe<Scalars['Boolean']>;
|
||||
/** Weight of the Product Variant. */
|
||||
weight?: InputMaybe<Scalars['WeightScalar']>;
|
||||
/** Added in Saleor 3.1. Determines if variant is in preorder. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Determines if variant is in preorder.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
preorder?: InputMaybe<PreorderSettingsInput>;
|
||||
/** Added in Saleor 3.1. Determines maximum quantity of `ProductVariant`,that can be bought in a single checkout. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Determines maximum quantity of `ProductVariant`,that can be bought in a single checkout.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
quantityLimitPerCustomer?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
@ -3717,7 +3869,11 @@ export type PublishableChannelListingInput = {
|
|||
* DEPRECATED: this field will be removed in Saleor 4.0. Use `publishedAt` field instead.
|
||||
*/
|
||||
publicationDate?: InputMaybe<Scalars['Date']>;
|
||||
/** Added in Saleor 3.3. Publication date time. ISO 8601 standard. */
|
||||
/**
|
||||
* Publication date time. ISO 8601 standard.
|
||||
*
|
||||
* Added in Saleor 3.3.
|
||||
*/
|
||||
publishedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
|
@ -3964,9 +4120,17 @@ export type ShopSettingsInput = {
|
|||
defaultWeightUnit?: InputMaybe<WeightUnitsEnum>;
|
||||
/** Enable automatic fulfillment for all digital products. */
|
||||
automaticFulfillmentDigitalProducts?: InputMaybe<Scalars['Boolean']>;
|
||||
/** Added in Saleor 3.1. Enable automatic approval of all new fulfillments. */
|
||||
/**
|
||||
* Enable automatic approval of all new fulfillments.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
fulfillmentAutoApprove?: InputMaybe<Scalars['Boolean']>;
|
||||
/** Added in Saleor 3.1. Enable ability to approve fulfillments which are unpaid. */
|
||||
/**
|
||||
* Enable ability to approve fulfillments which are unpaid.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
fulfillmentAllowUnpaid?: InputMaybe<Scalars['Boolean']>;
|
||||
/** Default number of max downloads per digital content URL. */
|
||||
defaultDigitalMaxDownloads?: InputMaybe<Scalars['Int']>;
|
||||
|
@ -3978,11 +4142,25 @@ export type ShopSettingsInput = {
|
|||
defaultMailSenderAddress?: InputMaybe<Scalars['String']>;
|
||||
/** URL of a view where customers can set their password. */
|
||||
customerSetPasswordUrl?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.1. Default number of minutes stock will be reserved for anonymous checkout. Enter 0 or null to disable. */
|
||||
/**
|
||||
* Default number of minutes stock will be reserved for anonymous checkout. Enter 0 or null to disable.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
reserveStockDurationAnonymousUser?: InputMaybe<Scalars['Int']>;
|
||||
/** Added in Saleor 3.1. Default number of minutes stock will be reserved for authenticated checkout. Enter 0 or null to disable. */
|
||||
/**
|
||||
* Default number of minutes stock will be reserved for authenticated checkout. Enter 0 or null to disable.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
reserveStockDurationAuthenticatedUser?: InputMaybe<Scalars['Int']>;
|
||||
/** Added in Saleor 3.1. Default number of maximum line quantity in single checkout. Minimum possible value is 1, default value is 50. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Default number of maximum line quantity in single checkout. Minimum possible value is 1, default value is 50.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
limitQuantityPerCheckout?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
@ -4275,7 +4453,11 @@ export type VoucherInput = {
|
|||
discountValueType?: InputMaybe<DiscountValueTypeEnum>;
|
||||
/** Products discounted by the voucher. */
|
||||
products?: InputMaybe<Array<Scalars['ID']>>;
|
||||
/** Added in Saleor 3.1. Variants discounted by the voucher. */
|
||||
/**
|
||||
* Variants discounted by the voucher.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*/
|
||||
variants?: InputMaybe<Array<Scalars['ID']>>;
|
||||
/** Collections discounted by the voucher. */
|
||||
collections?: InputMaybe<Array<Scalars['ID']>>;
|
||||
|
@ -4389,9 +4571,21 @@ export type WarehouseUpdateInput = {
|
|||
name?: InputMaybe<Scalars['String']>;
|
||||
/** Address of the warehouse. */
|
||||
address?: InputMaybe<AddressInput>;
|
||||
/** Added in Saleor 3.1. Click and collect options: local, all or disabled. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Click and collect options: local, all or disabled.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
clickAndCollectOption?: InputMaybe<WarehouseClickAndCollectOptionEnum>;
|
||||
/** Added in Saleor 3.1. Visibility of warehouse stocks. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Visibility of warehouse stocks.
|
||||
*
|
||||
* Added in Saleor 3.1.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
isPrivate?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
|
@ -4416,7 +4610,13 @@ export type WebhookCreateInput = {
|
|||
isActive?: InputMaybe<Scalars['Boolean']>;
|
||||
/** The secret key used to create a hash signature with each payload. */
|
||||
secretKey?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.2. Subscription query used to define a webhook payload. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Subscription query used to define a webhook payload.
|
||||
*
|
||||
* Added in Saleor 3.2.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
query?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
@ -4745,7 +4945,13 @@ export type WebhookUpdateInput = {
|
|||
isActive?: InputMaybe<Scalars['Boolean']>;
|
||||
/** Use to create a hash signature with each payload. */
|
||||
secretKey?: InputMaybe<Scalars['String']>;
|
||||
/** Added in Saleor 3.2. Subscription query used to define a webhook payload. Note: this feature is in a preview state and can be subject to changes at later point. */
|
||||
/**
|
||||
* Subscription query used to define a webhook payload.
|
||||
*
|
||||
* Added in Saleor 3.2.
|
||||
*
|
||||
* Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
*/
|
||||
query?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
@ -5826,6 +6032,8 @@ export type ProductVariantFragment = { __typename: 'ProductVariant', id: string,
|
|||
|
||||
export type ExportFileFragment = { __typename: 'ExportFile', id: string, status: JobStatusEnum, url: string | null };
|
||||
|
||||
export type ProductListAttributeFragment = { __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string }, values: Array<{ __typename: 'AttributeValue', id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> };
|
||||
|
||||
export type ShippingZoneFragment = { __typename: 'ShippingZone', id: string, name: string, description: string | null, countries: Array<{ __typename: 'CountryDisplay', code: string, country: string }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> };
|
||||
|
||||
export type ShippingMethodWithPostalCodesFragment = { __typename: 'ShippingMethodType', id: string, postalCodeRules: Array<{ __typename: 'ShippingMethodPostalCodeRule', id: string, inclusionType: PostalCodeRuleInclusionTypeEnum | null, start: string | null, end: string | null }> | null };
|
||||
|
@ -7020,14 +7228,6 @@ export type ProductMediaByIdQueryVariables = Exact<{
|
|||
|
||||
export type ProductMediaByIdQuery = { __typename: 'Query', product: { __typename: 'Product', id: string, name: string, mainImage: { __typename: 'ProductMedia', id: string, alt: string, url: string, type: ProductMediaType, oembedData: any } | null, media: Array<{ __typename: 'ProductMedia', id: string, url: string, alt: string, type: ProductMediaType, oembedData: any }> | null } | null };
|
||||
|
||||
export type AvailableInGridAttributesQueryVariables = Exact<{
|
||||
first: Scalars['Int'];
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type AvailableInGridAttributesQuery = { __typename: 'Query', availableInGrid: { __typename: 'AttributeCountableConnection', totalCount: number | null, edges: Array<{ __typename: 'AttributeCountableEdge', node: { __typename: 'Attribute', id: string, name: string | null } }>, pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null } } | null };
|
||||
|
||||
export type GridAttributesQueryVariables = Exact<{
|
||||
ids: Array<Scalars['ID']> | Scalars['ID'];
|
||||
}>;
|
||||
|
@ -7065,6 +7265,15 @@ export type SearchAttributeValuesQueryVariables = Exact<{
|
|||
|
||||
export type SearchAttributeValuesQuery = { __typename: 'Query', attribute: { __typename: 'Attribute', id: string, choices: { __typename: 'AttributeValueCountableConnection', edges: Array<{ __typename: 'AttributeValueCountableEdge', node: { __typename: 'AttributeValue', richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }>, pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null } } | null } | null };
|
||||
|
||||
export type SearchAvailableInGridAttributesQueryVariables = Exact<{
|
||||
first: Scalars['Int'];
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
query: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type SearchAvailableInGridAttributesQuery = { __typename: 'Query', availableInGrid: { __typename: 'AttributeCountableConnection', totalCount: number | null, edges: Array<{ __typename: 'AttributeCountableEdge', node: { __typename: 'Attribute', id: string, name: string | null } }>, pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null } } | null };
|
||||
|
||||
export type SearchAvailablePageAttributesQueryVariables = Exact<{
|
||||
id: Scalars['ID'];
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface UseSearchResult<TData, TVariables extends SearchVariables> {
|
|||
loadMore: () => void;
|
||||
result: QueryResult<TData, TVariables>;
|
||||
search: (query: string) => void;
|
||||
query: string;
|
||||
}
|
||||
export type UseSearchOpts<TVariables extends SearchVariables> = Partial<{
|
||||
skip: boolean;
|
||||
|
@ -45,6 +46,7 @@ function makeSearch<TData, TVariables extends SearchVariables>(
|
|||
});
|
||||
|
||||
return {
|
||||
query: searchQuery,
|
||||
loadMore: () => loadMoreFn(result),
|
||||
result,
|
||||
search: debouncedSearch
|
||||
|
|
|
@ -20,7 +20,10 @@ export interface ChangeEvent<TData = any> {
|
|||
}
|
||||
export type SubmitPromise<TData = any> = Promise<TData>;
|
||||
|
||||
export type FormChange = (event: ChangeEvent, cb?: () => void) => void;
|
||||
export type FormChange<T = any> = (
|
||||
event: ChangeEvent<T>,
|
||||
cb?: () => void
|
||||
) => void;
|
||||
|
||||
export type FormErrors<T> = {
|
||||
[field in keyof T]?: string | React.ReactNode;
|
||||
|
|
|
@ -221,6 +221,10 @@ export const buttonMessages = defineMessages({
|
|||
defaultMessage: "Remove",
|
||||
description: "button"
|
||||
},
|
||||
reset: {
|
||||
defaultMessage: "Reset",
|
||||
description: "button"
|
||||
},
|
||||
save: {
|
||||
defaultMessage: "Save",
|
||||
description: "button"
|
||||
|
|
|
@ -48,6 +48,7 @@ import React from "react";
|
|||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { columnsMessages } from "./messages";
|
||||
import ProductListAttribute from "./ProductListAttribute";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
|
@ -69,7 +70,10 @@ const useStyles = makeStyles(
|
|||
}
|
||||
},
|
||||
colAttribute: {
|
||||
width: 150
|
||||
width: 200,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap"
|
||||
},
|
||||
colFill: {
|
||||
padding: 0,
|
||||
|
@ -123,7 +127,6 @@ interface ProductListProps
|
|||
activeAttributeSortId: string;
|
||||
gridAttributes: RelayToFlat<GridAttributesQuery["grid"]>;
|
||||
products: RelayToFlat<ProductListQuery["products"]>;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const ProductList: React.FC<ProductListProps> = props => {
|
||||
|
@ -427,19 +430,10 @@ export const ProductList: React.FC<ProductListProps> = props => {
|
|||
gridAttribute
|
||||
)}
|
||||
>
|
||||
{maybe<React.ReactNode>(() => {
|
||||
const attribute = product.attributes.find(
|
||||
attribute =>
|
||||
attribute.attribute.id ===
|
||||
getAttributeIdFromColumnValue(gridAttribute)
|
||||
);
|
||||
if (attribute) {
|
||||
return attribute.values
|
||||
.map(value => value.name)
|
||||
.join(", ");
|
||||
}
|
||||
return "-";
|
||||
}, <Skeleton />)}
|
||||
<ProductListAttribute
|
||||
attribute={gridAttribute}
|
||||
productAttributes={product?.attributes}
|
||||
/>
|
||||
</TableCell>
|
||||
))}
|
||||
<DisplayColumn
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import { ProductListAttributeFragment } from "@saleor/graphql";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ProductListAttribute from "./ProductListAttribute";
|
||||
|
||||
const attributes: ProductListAttributeFragment[] = [
|
||||
{
|
||||
__typename: "SelectedAttribute",
|
||||
attribute: {
|
||||
__typename: "Attribute",
|
||||
id: "1"
|
||||
},
|
||||
values: [
|
||||
{
|
||||
id: "QXR0cmlidXRlVmFsdWU6MTEz",
|
||||
name: "2022-03-11",
|
||||
slug: "72_37",
|
||||
file: null,
|
||||
reference: null,
|
||||
boolean: null,
|
||||
date: "2022-03-11",
|
||||
dateTime: null,
|
||||
value: "",
|
||||
__typename: "AttributeValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
attribute: {
|
||||
id: "2",
|
||||
__typename: "Attribute"
|
||||
},
|
||||
values: [
|
||||
{
|
||||
id: "QXR0cmlidXRlVmFsdWU6MTE1",
|
||||
name: "2022-03-01 16:24:00+01:00",
|
||||
slug: "74_38",
|
||||
file: null,
|
||||
reference: null,
|
||||
boolean: null,
|
||||
date: null,
|
||||
dateTime: "2022-03-01T15:24:00+00:00",
|
||||
value: "",
|
||||
__typename: "AttributeValue"
|
||||
}
|
||||
],
|
||||
__typename: "SelectedAttribute"
|
||||
},
|
||||
{
|
||||
attribute: {
|
||||
id: "3",
|
||||
__typename: "Attribute"
|
||||
},
|
||||
values: [
|
||||
{
|
||||
id: "QXR0cmlidXRlOjMw",
|
||||
name: "Lorem Ipsum",
|
||||
slug: "72_2",
|
||||
file: null,
|
||||
reference: "UGFnZToy",
|
||||
boolean: null,
|
||||
date: null,
|
||||
dateTime: null,
|
||||
value: "",
|
||||
__typename: "AttributeValue"
|
||||
},
|
||||
{
|
||||
id: "QXR0cmlidXRlOjMx",
|
||||
name: "Dolor Sit",
|
||||
slug: "72_3",
|
||||
file: null,
|
||||
reference: "UGFnZToz",
|
||||
boolean: null,
|
||||
date: null,
|
||||
dateTime: null,
|
||||
value: "",
|
||||
__typename: "AttributeValue"
|
||||
}
|
||||
],
|
||||
__typename: "SelectedAttribute"
|
||||
}
|
||||
];
|
||||
|
||||
storiesOf("Views / Products / Product list / Attribute display", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => (
|
||||
<ProductListAttribute
|
||||
attribute="attribute:3"
|
||||
productAttributes={attributes}
|
||||
/>
|
||||
))
|
||||
.add("date", () => (
|
||||
<ProductListAttribute
|
||||
attribute="attribute:1"
|
||||
productAttributes={attributes}
|
||||
/>
|
||||
))
|
||||
.add("datetime", () => (
|
||||
<ProductListAttribute
|
||||
attribute="attribute:2"
|
||||
productAttributes={attributes}
|
||||
/>
|
||||
));
|
44
src/products/components/ProductList/ProductListAttribute.tsx
Normal file
44
src/products/components/ProductList/ProductListAttribute.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import Date, { DateTime } from "@saleor/components/Date";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import { ProductListAttributeFragment } from "@saleor/graphql";
|
||||
import React from "react";
|
||||
|
||||
import { getAttributeIdFromColumnValue } from "../ProductListPage/utils";
|
||||
|
||||
export interface ProductListAttributeProps {
|
||||
attribute: string;
|
||||
productAttributes: ProductListAttributeFragment[];
|
||||
}
|
||||
|
||||
const ProductListAttribute: React.FC<ProductListAttributeProps> = ({
|
||||
attribute: gridAttribute,
|
||||
productAttributes
|
||||
}) => {
|
||||
if (!productAttributes) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
const productAttribute = productAttributes.find(
|
||||
attribute =>
|
||||
attribute.attribute.id === getAttributeIdFromColumnValue(gridAttribute)
|
||||
);
|
||||
if (productAttribute) {
|
||||
if (productAttribute.values.length) {
|
||||
if (productAttribute.values[0].date) {
|
||||
return <Date date={productAttribute.values[0].date} />;
|
||||
}
|
||||
if (productAttribute.values[0].dateTime) {
|
||||
return <DateTime date={productAttribute.values[0].dateTime} />;
|
||||
}
|
||||
}
|
||||
|
||||
const textValue = productAttribute.values
|
||||
.map(value => value.name)
|
||||
.join(", ");
|
||||
|
||||
return <span title={textValue}>{textValue}</span>;
|
||||
}
|
||||
return <span>-</span>;
|
||||
};
|
||||
|
||||
export default ProductListAttribute;
|
|
@ -6,20 +6,19 @@ import {
|
|||
} from "@saleor/apps/useExtensions";
|
||||
import { ButtonWithSelect } from "@saleor/components/ButtonWithSelect";
|
||||
import CardMenu from "@saleor/components/CardMenu";
|
||||
import ColumnPicker, {
|
||||
ColumnPickerChoice
|
||||
} from "@saleor/components/ColumnPicker";
|
||||
import ColumnPicker from "@saleor/components/ColumnPicker";
|
||||
import Container from "@saleor/components/Container";
|
||||
import { getByName } from "@saleor/components/Filter/utils";
|
||||
import FilterBar from "@saleor/components/FilterBar";
|
||||
import LimitReachedAlert from "@saleor/components/LimitReachedAlert";
|
||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import { ProductListColumns } from "@saleor/config";
|
||||
import {
|
||||
AvailableInGridAttributesQuery,
|
||||
GridAttributesQuery,
|
||||
ProductListQuery,
|
||||
RefreshLimitsQuery
|
||||
RefreshLimitsQuery,
|
||||
SearchAvailableInGridAttributesQuery
|
||||
} from "@saleor/graphql";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
|
@ -44,6 +43,7 @@ import {
|
|||
ProductFilterKeys,
|
||||
ProductListFilterOpts
|
||||
} from "./filters";
|
||||
import { getAttributeColumnValue } from "./utils";
|
||||
|
||||
export interface ProductListPageProps
|
||||
extends PageListProps<ProductListColumns>,
|
||||
|
@ -54,15 +54,17 @@ export interface ProductListPageProps
|
|||
ChannelProps {
|
||||
activeAttributeSortId: string;
|
||||
availableInGridAttributes: RelayToFlat<
|
||||
AvailableInGridAttributesQuery["availableInGrid"]
|
||||
SearchAvailableInGridAttributesQuery["availableInGrid"]
|
||||
>;
|
||||
channelsCount: number;
|
||||
columnQuery: string;
|
||||
currencySymbol: string;
|
||||
gridAttributes: RelayToFlat<GridAttributesQuery["grid"]>;
|
||||
limits: RefreshLimitsQuery["shop"]["limits"];
|
||||
totalGridAttributes: number;
|
||||
products: RelayToFlat<ProductListQuery["products"]>;
|
||||
onExport: () => void;
|
||||
onColumnQueryChange: (query: string) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -87,6 +89,7 @@ const useStyles = makeStyles(
|
|||
export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||
const {
|
||||
channelsCount,
|
||||
columnQuery,
|
||||
currencySymbol,
|
||||
currentTab,
|
||||
defaultSettings,
|
||||
|
@ -102,6 +105,7 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
totalGridAttributes,
|
||||
onAdd,
|
||||
onAll,
|
||||
onColumnQueryChange,
|
||||
onExport,
|
||||
onFetchMore,
|
||||
onFilterChange,
|
||||
|
@ -117,14 +121,11 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
const intl = useIntl();
|
||||
const classes = useStyles(props);
|
||||
|
||||
const handleSave = (columns: ProductListColumns[]) =>
|
||||
onUpdateListSettings("columns", columns);
|
||||
|
||||
const filterStructure = createFilterStructure(intl, filterOpts);
|
||||
|
||||
const filterDependency = filterStructure.find(getByName("channel"));
|
||||
|
||||
const columns: ColumnPickerChoice[] = [
|
||||
const staticColumns = [
|
||||
{
|
||||
label: intl.formatMessage(columnsMessages.availability),
|
||||
value: "availability" as ProductListColumns
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage(columnsMessages.price),
|
||||
value: "price" as ProductListColumns
|
||||
|
@ -136,11 +137,37 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
{
|
||||
label: intl.formatMessage(columnsMessages.updatedAt),
|
||||
value: "date" as ProductListColumns
|
||||
},
|
||||
...availableInGridAttributes.map(attribute => ({
|
||||
}
|
||||
];
|
||||
|
||||
const initialColumnsChoices = React.useMemo(() => {
|
||||
const selectedStaticColumns = staticColumns.filter(column =>
|
||||
(settings.columns || []).includes(column.value)
|
||||
);
|
||||
const selectedAttributeColumns = gridAttributes.map(attribute => ({
|
||||
label: attribute.name,
|
||||
value: `attribute:${attribute.id}`
|
||||
}))
|
||||
value: getAttributeColumnValue(attribute.id)
|
||||
}));
|
||||
|
||||
return [...selectedStaticColumns, ...selectedAttributeColumns];
|
||||
}, [gridAttributes, settings.columns]);
|
||||
|
||||
const handleSave = (columns: ProductListColumns[]) =>
|
||||
onUpdateListSettings("columns", columns);
|
||||
|
||||
const filterStructure = createFilterStructure(intl, filterOpts);
|
||||
|
||||
const filterDependency = filterStructure.find(getByName("channel"));
|
||||
|
||||
const availableColumns: MultiAutocompleteChoiceType[] = [
|
||||
...staticColumns,
|
||||
...availableInGridAttributes.map(
|
||||
attribute =>
|
||||
({
|
||||
label: attribute.name,
|
||||
value: getAttributeColumnValue(attribute.id)
|
||||
} as MultiAutocompleteChoiceType)
|
||||
)
|
||||
];
|
||||
|
||||
const limitReached = isLimitReached(limits, "productVariants");
|
||||
|
@ -189,15 +216,13 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
>
|
||||
<ColumnPicker
|
||||
className={classes.columnPicker}
|
||||
columns={columns}
|
||||
availableColumns={availableColumns}
|
||||
initialColumns={initialColumnsChoices}
|
||||
defaultColumns={defaultSettings.columns}
|
||||
hasMore={hasMore}
|
||||
initialColumns={settings.columns}
|
||||
total={
|
||||
columns.length -
|
||||
availableInGridAttributes.length +
|
||||
totalGridAttributes
|
||||
}
|
||||
loading={loading}
|
||||
query={columnQuery}
|
||||
onQueryChange={onColumnQueryChange}
|
||||
onFetchMore={onFetchMore}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
|
@ -247,7 +272,6 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
/>
|
||||
<ProductList
|
||||
{...listProps}
|
||||
loading={loading}
|
||||
gridAttributes={gridAttributes}
|
||||
settings={settings}
|
||||
selectedChannelId={selectedChannelId}
|
||||
|
|
|
@ -2,10 +2,7 @@ import { gql } from "@apollo/client";
|
|||
|
||||
export const initialProductFilterAttributesQuery = gql`
|
||||
query InitialProductFilterAttributes {
|
||||
attributes(
|
||||
first: 100
|
||||
filter: { filterableInDashboard: true, type: PRODUCT_TYPE }
|
||||
) {
|
||||
attributes(first: 100, filter: { type: PRODUCT_TYPE }) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
|
@ -83,12 +80,7 @@ export const productListQuery = gql`
|
|||
...ProductWithChannelListings
|
||||
updatedAt
|
||||
attributes @include(if: $hasSelectedAttributes) {
|
||||
attribute {
|
||||
id
|
||||
}
|
||||
values {
|
||||
...AttributeValue
|
||||
}
|
||||
...ProductListAttribute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -253,31 +245,6 @@ export const productMediaQuery = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
export const availableInGridAttributes = gql`
|
||||
query AvailableInGridAttributes($first: Int!, $after: String) {
|
||||
availableInGrid: attributes(
|
||||
first: $first
|
||||
after: $after
|
||||
filter: {
|
||||
availableInGrid: true
|
||||
isVariantOnly: false
|
||||
type: PRODUCT_TYPE
|
||||
}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const gridAttributes = gql`
|
||||
query GridAttributes($ids: [ID!]!) {
|
||||
grid: attributes(first: 25, filter: { ids: $ids }) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { DialogContentText } from "@material-ui/core";
|
||||
import { filterable } from "@saleor/attributes/utils/data";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import useAppChannel from "@saleor/components/AppLayout/AppChannelContext";
|
||||
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||
|
@ -15,7 +16,6 @@ import {
|
|||
import { Task } from "@saleor/containers/BackgroundTasks/types";
|
||||
import {
|
||||
ProductListQueryVariables,
|
||||
useAvailableInGridAttributesQuery,
|
||||
useGridAttributesQuery,
|
||||
useInitialProductFilterAttributesQuery,
|
||||
useInitialProductFilterCategoriesQuery,
|
||||
|
@ -54,6 +54,7 @@ import {
|
|||
} from "@saleor/products/urls";
|
||||
import useAttributeSearch from "@saleor/searches/useAttributeSearch";
|
||||
import useAttributeValueSearch from "@saleor/searches/useAttributeValueSearch";
|
||||
import useAvailableInGridAttributesSearch from "@saleor/searches/useAvailableInGridAttributesSearch";
|
||||
import useCategorySearch from "@saleor/searches/useCategorySearch";
|
||||
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
|
||||
import useProductTypeSearch from "@saleor/searches/useProductTypeSearch";
|
||||
|
@ -276,12 +277,9 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
[params, settings.rowNumber]
|
||||
);
|
||||
|
||||
function filterColumnIds(columns: ProductListColumns[]) {
|
||||
return columns
|
||||
.filter(isAttributeColumnValue)
|
||||
.map(getAttributeIdFromColumnValue);
|
||||
}
|
||||
const filteredColumnIds = filterColumnIds(settings.columns);
|
||||
const filteredColumnIds = settings.columns
|
||||
.filter(isAttributeColumnValue)
|
||||
.map(getAttributeIdFromColumnValue);
|
||||
|
||||
const { data, loading, refetch } = useProductListQuery({
|
||||
displayLoader: true,
|
||||
|
@ -292,11 +290,15 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
}
|
||||
});
|
||||
|
||||
const availableInGridAttributes = useAvailableInGridAttributesQuery({
|
||||
variables: { first: 24 }
|
||||
const availableInGridAttributesOpts = useAvailableInGridAttributesSearch({
|
||||
variables: {
|
||||
...DEFAULT_INITIAL_SEARCH_DATA,
|
||||
first: 5
|
||||
}
|
||||
});
|
||||
const gridAttributes = useGridAttributesQuery({
|
||||
variables: { ids: filteredColumnIds }
|
||||
variables: { ids: filteredColumnIds },
|
||||
skip: filteredColumnIds.length === 0
|
||||
});
|
||||
|
||||
const [
|
||||
|
@ -319,7 +321,9 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
|
||||
const filterOpts = getFilterOpts(
|
||||
params,
|
||||
mapEdgesToItems(initialFilterAttributes?.attributes) || [],
|
||||
(mapEdgesToItems(initialFilterAttributes?.attributes) || []).filter(
|
||||
filterable
|
||||
),
|
||||
searchAttributeValues,
|
||||
{
|
||||
initial: mapEdgesToItems(initialFilterCategories?.categories) || [],
|
||||
|
@ -353,8 +357,9 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
}}
|
||||
onSort={handleSort}
|
||||
availableInGridAttributes={
|
||||
mapEdgesToItems(availableInGridAttributes?.data?.availableInGrid) ||
|
||||
[]
|
||||
mapEdgesToItems(
|
||||
availableInGridAttributesOpts.result?.data?.availableInGrid
|
||||
) || []
|
||||
}
|
||||
currencySymbol={selectedChannel?.currencyCode || ""}
|
||||
currentTab={currentTab}
|
||||
|
@ -362,48 +367,27 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
filterOpts={filterOpts}
|
||||
gridAttributes={mapEdgesToItems(gridAttributes?.data?.grid) || []}
|
||||
totalGridAttributes={maybe(
|
||||
() => availableInGridAttributes.data.availableInGrid.totalCount,
|
||||
() =>
|
||||
availableInGridAttributesOpts.result.data.availableInGrid
|
||||
.totalCount,
|
||||
0
|
||||
)}
|
||||
settings={settings}
|
||||
loading={availableInGridAttributes.loading || gridAttributes.loading}
|
||||
loading={
|
||||
availableInGridAttributesOpts.result.loading || gridAttributes.loading
|
||||
}
|
||||
hasMore={maybe(
|
||||
() =>
|
||||
availableInGridAttributes.data.availableInGrid.pageInfo.hasNextPage,
|
||||
availableInGridAttributesOpts.result.data.availableInGrid.pageInfo
|
||||
.hasNextPage,
|
||||
false
|
||||
)}
|
||||
onAdd={() => navigate(productAddUrl())}
|
||||
disabled={loading}
|
||||
limits={limitOpts.data?.shop.limits}
|
||||
products={mapEdgesToItems(data?.products)}
|
||||
onFetchMore={() =>
|
||||
availableInGridAttributes.loadMore(
|
||||
(prev, next) => {
|
||||
if (
|
||||
prev.availableInGrid.pageInfo.endCursor ===
|
||||
next.availableInGrid.pageInfo.endCursor
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
availableInGrid: {
|
||||
...prev.availableInGrid,
|
||||
edges: [
|
||||
...prev.availableInGrid.edges,
|
||||
...next.availableInGrid.edges
|
||||
],
|
||||
pageInfo: next.availableInGrid.pageInfo
|
||||
}
|
||||
};
|
||||
},
|
||||
{
|
||||
after:
|
||||
availableInGridAttributes.data.availableInGrid.pageInfo
|
||||
.endCursor
|
||||
}
|
||||
)
|
||||
}
|
||||
onColumnQueryChange={availableInGridAttributesOpts.search}
|
||||
onFetchMore={availableInGridAttributesOpts.loadMore}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
|
@ -438,6 +422,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
onExport={() => openModal("export")}
|
||||
channelsCount={availableChannels?.length}
|
||||
selectedChannelId={selectedChannel?.id}
|
||||
columnQuery={availableInGridAttributesOpts.query}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "delete"}
|
||||
|
|
66
src/searches/useAvailableInGridAttributesSearch.ts
Normal file
66
src/searches/useAvailableInGridAttributesSearch.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { gql } from "@apollo/client";
|
||||
import {
|
||||
SearchAvailableInGridAttributesDocument,
|
||||
SearchAvailableInGridAttributesQuery,
|
||||
SearchAvailableInGridAttributesQueryVariables
|
||||
} from "@saleor/graphql";
|
||||
import makeSearch from "@saleor/hooks/makeSearch";
|
||||
|
||||
export const availableInGridAttributes = gql`
|
||||
query SearchAvailableInGridAttributes(
|
||||
$first: Int!
|
||||
$after: String
|
||||
$query: String!
|
||||
) {
|
||||
availableInGrid: attributes(
|
||||
first: $first
|
||||
after: $after
|
||||
filter: { isVariantOnly: false, type: PRODUCT_TYPE, search: $query }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default makeSearch<
|
||||
SearchAvailableInGridAttributesQuery,
|
||||
SearchAvailableInGridAttributesQueryVariables
|
||||
>(SearchAvailableInGridAttributesDocument, result => {
|
||||
if (result.data?.availableInGrid.pageInfo.hasNextPage) {
|
||||
result.loadMore(
|
||||
(prev, next) => {
|
||||
if (
|
||||
prev.availableInGrid.pageInfo.endCursor ===
|
||||
next.availableInGrid.pageInfo.endCursor
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
availableInGrid: {
|
||||
...prev.availableInGrid,
|
||||
edges: [
|
||||
...prev.availableInGrid.edges,
|
||||
...next.availableInGrid.edges
|
||||
],
|
||||
pageInfo: next.availableInGrid.pageInfo
|
||||
}
|
||||
} as SearchAvailableInGridAttributesQuery;
|
||||
},
|
||||
{
|
||||
...result.variables,
|
||||
after: result.data.availableInGrid.pageInfo.endCursor
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load diff
|
@ -24,14 +24,6 @@ const props: AttributeListPageProps = {
|
|||
...filterPageProps,
|
||||
attributes,
|
||||
filterOpts: {
|
||||
availableInGrid: {
|
||||
active: false,
|
||||
value: false
|
||||
},
|
||||
filterableInDashboard: {
|
||||
active: false,
|
||||
value: false
|
||||
},
|
||||
filterableInStorefront: {
|
||||
active: false,
|
||||
value: false
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import ColumnPicker, {
|
||||
ColumnPickerProps
|
||||
} from "@saleor/components/ColumnPicker";
|
||||
import { ColumnPickerChoice } from "@saleor/components/ColumnPicker/ColumnPickerContent";
|
||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import Decorator from "../../Decorator";
|
||||
|
||||
const columns: ColumnPickerChoice[] = [
|
||||
const availableColumns: MultiAutocompleteChoiceType[] = [
|
||||
{ label: "Name", value: "name" },
|
||||
{ label: "Value", value: "value" },
|
||||
{ label: "Type", value: "type" },
|
||||
|
@ -25,11 +25,16 @@ const columns: ColumnPickerChoice[] = [
|
|||
];
|
||||
|
||||
const props: ColumnPickerProps = {
|
||||
columns,
|
||||
defaultColumns: [1, 3].map(index => columns[index].value),
|
||||
initialColumns: [1, 3, 4, 6].map(index => columns[index].value),
|
||||
availableColumns,
|
||||
defaultColumns: [1, 3].map(index => availableColumns[index].value),
|
||||
initialColumns: [1, 3, 4, 6].map(index => availableColumns[index].value),
|
||||
initialOpen: true,
|
||||
onSave: () => undefined
|
||||
hasMore: false,
|
||||
onFetchMore: () => undefined,
|
||||
loading: false,
|
||||
onSave: () => undefined,
|
||||
query: "",
|
||||
onQueryChange: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Generics / Column picker", module)
|
||||
|
@ -41,6 +46,4 @@ storiesOf("Generics / Column picker", module)
|
|||
.addDecorator(CardDecorator)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ColumnPicker {...props} />)
|
||||
.add("loading", () => (
|
||||
<ColumnPicker {...props} hasMore={true} onFetchMore={() => undefined} />
|
||||
));
|
||||
.add("loading", () => <ColumnPicker {...props} loading hasMore />);
|
||||
|
|
|
@ -39,6 +39,8 @@ const props: ProductListPageProps = {
|
|||
activeAttributeSortId: undefined,
|
||||
availableInGridAttributes: attributes,
|
||||
channelsCount: 6,
|
||||
columnQuery: "",
|
||||
onColumnQueryChange: () => undefined,
|
||||
currencySymbol: "USD",
|
||||
defaultSettings: defaultListSettings[ListViews.PRODUCT_LIST],
|
||||
filterOpts: productListFilterOpts,
|
||||
|
|
|
@ -60,6 +60,7 @@ function useAttributeValueSearchHandler(
|
|||
}, [state.id]);
|
||||
|
||||
return {
|
||||
query: state.query,
|
||||
loadMore,
|
||||
search: handleSearch,
|
||||
reset,
|
||||
|
|
Loading…
Reference in a new issue