Experimental filters: add support for constrains on UI (#3969)
This commit is contained in:
parent
23bb5976c6
commit
5a6c255004
8 changed files with 1081 additions and 669 deletions
5
.changeset/calm-poets-unite.md
Normal file
5
.changeset/calm-poets-unite.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Experimental filters: add support for constrains in MacawUI
|
1495
package-lock.json
generated
1495
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -34,7 +34,7 @@
|
||||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||||
"@material-ui/styles": "^4.11.4",
|
"@material-ui/styles": "^4.11.4",
|
||||||
"@reach/auto-id": "^0.16.0",
|
"@reach/auto-id": "^0.16.0",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.106",
|
"@saleor/macaw-ui": "0.8.0-pre.107",
|
||||||
"@saleor/sdk": "0.6.0",
|
"@saleor/sdk": "0.6.0",
|
||||||
"@sentry/react": "^6.0.0",
|
"@sentry/react": "^6.0.0",
|
||||||
"@types/faker": "^5.1.6",
|
"@types/faker": "^5.1.6",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { _ExperimentalFilters, Box, Divider } from "@saleor/macaw-ui/next";
|
import { Box, Divider, Skeleton } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export const LoadingFiltersArea = () => (
|
export const LoadingFiltersArea = () => (
|
||||||
|
@ -11,16 +11,16 @@ export const LoadingFiltersArea = () => (
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
>
|
>
|
||||||
<Box display="flex" flexDirection="column" gap={3} height="100%">
|
<Box display="flex" flexDirection="column" gap={3} height="100%">
|
||||||
<_ExperimentalFilters.Skeleton height={7} />
|
<Skeleton height={7} />
|
||||||
<_ExperimentalFilters.Skeleton height={7} />
|
<Skeleton height={7} />
|
||||||
<_ExperimentalFilters.Skeleton height={7} />
|
<Skeleton height={7} />
|
||||||
</Box>
|
</Box>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box display="flex" gap={4} justifyContent="space-between">
|
<Box display="flex" gap={4} justifyContent="space-between">
|
||||||
<_ExperimentalFilters.Skeleton height={7} __width="60px" />
|
<Skeleton height={7} __width="60px" />
|
||||||
<Box display="flex" gap={3}>
|
<Box display="flex" gap={3}>
|
||||||
<_ExperimentalFilters.Skeleton height={7} __width="60px" />
|
<Skeleton height={7} __width="60px" />
|
||||||
<_ExperimentalFilters.Skeleton height={7} __width="60px" />
|
<Skeleton height={7} __width="60px" />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const STATIC_CONDITIONS = {
|
||||||
channel: [{ type: "select", label: "is", value: "input-5" }],
|
channel: [{ type: "select", label: "is", value: "input-5" }],
|
||||||
productType: [
|
productType: [
|
||||||
{ type: "combobox", label: "is", value: "input-1" },
|
{ type: "combobox", label: "is", value: "input-1" },
|
||||||
{ type: "multiselect", label: "in", value: "input-2" }
|
{ type: "multiselect", label: "in", value: "input-2" },
|
||||||
],
|
],
|
||||||
isAvailable: [{ type: "select", label: "is", value: "input-1" }],
|
isAvailable: [{ type: "select", label: "is", value: "input-1" }],
|
||||||
isPublished: [{ type: "select", label: "is", value: "input-1" }],
|
isPublished: [{ type: "select", label: "is", value: "input-1" }],
|
||||||
|
@ -26,14 +26,13 @@ export const STATIC_CONDITIONS = {
|
||||||
giftCard: [{ type: "select", label: "is", value: "input-1" }],
|
giftCard: [{ type: "select", label: "is", value: "input-1" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const CONSTRAINTS = {
|
export const CONSTRAINTS = {
|
||||||
channel: {
|
channel: {
|
||||||
dependsOn: ["price", "isVisibleInListing"],
|
dependsOn: ["price", "isVisibleInListing", "isAvailable", "isPublished"],
|
||||||
removable: true,
|
removable: false,
|
||||||
disabled: ["left", "condition"]
|
disabled: ["left", "condition"],
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export const STATIC_OPTIONS: LeftOperand[] = [
|
export const STATIC_OPTIONS: LeftOperand[] = [
|
||||||
{ value: "price", label: "Price", type: "price", slug: "price" },
|
{ value: "price", label: "Price", type: "price", slug: "price" },
|
||||||
|
@ -45,12 +44,42 @@ export const STATIC_OPTIONS: LeftOperand[] = [
|
||||||
slug: "collection",
|
slug: "collection",
|
||||||
},
|
},
|
||||||
{ value: "channel", label: "Channel", type: "channel", slug: "channel" },
|
{ value: "channel", label: "Channel", type: "channel", slug: "channel" },
|
||||||
{ value: "productType", label: "Product Type", type: "productType", slug: "productType" },
|
{
|
||||||
{ value: "isAvailable", label: "Is available", type: "isAvailable", slug: "isAvailable" },
|
value: "productType",
|
||||||
{ value: "isPublished", label: "Is published", type: "isPublished", slug: "isPublished" },
|
label: "Product Type",
|
||||||
{ value: "isVisibleInListing", label: "Visible in listing", type: "isVisibleInListing", slug: "isVisibleInListing" },
|
type: "productType",
|
||||||
{ value: "hasCategory", label: "Has category", type: "hasCategory", slug: "hasCategory" },
|
slug: "productType",
|
||||||
{ value: "giftCard", label: "Has giftcard", type: "giftCard", slug: "giftCard" },
|
},
|
||||||
|
{
|
||||||
|
value: "isAvailable",
|
||||||
|
label: "Is available",
|
||||||
|
type: "isAvailable",
|
||||||
|
slug: "isAvailable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "isPublished",
|
||||||
|
label: "Is published",
|
||||||
|
type: "isPublished",
|
||||||
|
slug: "isPublished",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "isVisibleInListing",
|
||||||
|
label: "Visible in listing",
|
||||||
|
type: "isVisibleInListing",
|
||||||
|
slug: "isVisibleInListing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "hasCategory",
|
||||||
|
label: "Has category",
|
||||||
|
type: "hasCategory",
|
||||||
|
slug: "hasCategory",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "giftCard",
|
||||||
|
label: "Has giftcard",
|
||||||
|
type: "giftCard",
|
||||||
|
slug: "giftCard",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ATTRIBUTE_INPUT_TYPE_CONDITIONS = {
|
export const ATTRIBUTE_INPUT_TYPE_CONDITIONS = {
|
||||||
|
@ -69,38 +98,42 @@ export const ATTRIBUTE_INPUT_TYPE_CONDITIONS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAtributeInputType = (item: ConditionItem | null) => {
|
export const getAtributeInputType = (item: ConditionItem | null) => {
|
||||||
const result = Object.entries(ATTRIBUTE_INPUT_TYPE_CONDITIONS)
|
const result = Object.entries(ATTRIBUTE_INPUT_TYPE_CONDITIONS).find(
|
||||||
.find(([_, value]) =>
|
([_, value]) =>
|
||||||
value.find(entry => entry.type === item?.type && entry.label === item.label)
|
value.find(
|
||||||
)
|
entry => entry.type === item?.type && entry.label === item.label,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return result && result[0]
|
return result && result[0];
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type RowType = keyof typeof STATIC_CONDITIONS | "attribute";
|
||||||
export type RowType = keyof typeof STATIC_CONDITIONS | "attribute"
|
|
||||||
|
|
||||||
export const booleanOptionTrue = (type?: string) => ({
|
export const booleanOptionTrue = (type?: string) => ({
|
||||||
label: "Yes",
|
label: "Yes",
|
||||||
value: "true",
|
value: "true",
|
||||||
slug: "true",
|
slug: "true",
|
||||||
...({ type })
|
...{ type },
|
||||||
})
|
});
|
||||||
|
|
||||||
export const booleanOptionFalse = (type?: string) => ({
|
export const booleanOptionFalse = (type?: string) => ({
|
||||||
label: "No",
|
label: "No",
|
||||||
value: "false",
|
value: "false",
|
||||||
slug: "false",
|
slug: "false",
|
||||||
...({ type })
|
...{ type },
|
||||||
})
|
});
|
||||||
|
|
||||||
export const createBooleanOptions = (type?: string): ItemOption[] => [
|
export const createBooleanOptions = (type?: string): ItemOption[] => [
|
||||||
booleanOptionTrue(type),
|
booleanOptionTrue(type),
|
||||||
booleanOptionFalse(type)
|
booleanOptionFalse(type),
|
||||||
]
|
];
|
||||||
|
|
||||||
export const createBoleanOption = (flag: boolean, type?: string): ItemOption => {
|
export const createBoleanOption = (
|
||||||
if (flag) return booleanOptionTrue(type)
|
flag: boolean,
|
||||||
|
type?: string,
|
||||||
|
): ItemOption => {
|
||||||
|
if (flag) return booleanOptionTrue(type);
|
||||||
|
|
||||||
return booleanOptionFalse(type)
|
return booleanOptionFalse(type);
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,111 +1,134 @@
|
||||||
import { AttributeInput,DecimalFilterInput, GlobalIdFilterInput, ProductWhereInput } from "@dashboard/graphql";
|
import {
|
||||||
|
AttributeInput,
|
||||||
|
DecimalFilterInput,
|
||||||
|
GlobalIdFilterInput,
|
||||||
|
ProductWhereInput,
|
||||||
|
} from "@dashboard/graphql";
|
||||||
|
|
||||||
import { FilterContainer } from "./FilterElement";
|
import { FilterContainer } from "./FilterElement";
|
||||||
import { ConditionSelected } from "./FilterElement/ConditionSelected";
|
import { ConditionSelected } from "./FilterElement/ConditionSelected";
|
||||||
import { isItemOption, isItemOptionArray, isTuple } from "./FilterElement/ConditionValue";
|
import {
|
||||||
|
isItemOption,
|
||||||
|
isItemOptionArray,
|
||||||
|
isTuple,
|
||||||
|
} from "./FilterElement/ConditionValue";
|
||||||
|
|
||||||
type StaticQueryPart =
|
type StaticQueryPart =
|
||||||
| string
|
| string
|
||||||
| GlobalIdFilterInput
|
| GlobalIdFilterInput
|
||||||
| boolean
|
| boolean
|
||||||
| DecimalFilterInput
|
| DecimalFilterInput;
|
||||||
|
|
||||||
|
const createStaticQueryPart = (
|
||||||
|
selected: ConditionSelected,
|
||||||
|
): StaticQueryPart => {
|
||||||
|
if (!selected.conditionValue) return "";
|
||||||
|
|
||||||
const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => {
|
const { label } = selected.conditionValue;
|
||||||
if (!selected.conditionValue) return ""
|
const { value } = selected;
|
||||||
|
|
||||||
const { label } = selected.conditionValue
|
|
||||||
const { value } = selected
|
|
||||||
|
|
||||||
if (label === "lower") {
|
if (label === "lower") {
|
||||||
return { range: { lte: value } }
|
return { range: { lte: value } };
|
||||||
}
|
}
|
||||||
if (label === "greater") {
|
if (label === "greater") {
|
||||||
return { range: { gte: value } }
|
return { range: { gte: value } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTuple(value) && label === "between") {
|
if (isTuple(value) && label === "between") {
|
||||||
const [lte, gte] = value
|
const [lte, gte] = value;
|
||||||
return { range: { lte, gte } }
|
return { range: { lte, gte } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isItemOption(value)) {
|
if (isItemOption(value)) {
|
||||||
return { eq: value.value }
|
return { eq: value.value };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isItemOptionArray(value)) {
|
if (isItemOptionArray(value)) {
|
||||||
return { oneOf: value.map(x => x.value) }
|
return { oneOf: value.map(x => x.value) };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return { eq: value }
|
return { eq: value };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return { eq: value }
|
return { eq: value };
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return value;
|
||||||
}
|
};
|
||||||
|
|
||||||
const createAttributeQueryPart = (attributeSlug: string, selected: ConditionSelected): AttributeInput => {
|
const createAttributeQueryPart = (
|
||||||
if (!selected.conditionValue) return { slug: attributeSlug }
|
attributeSlug: string,
|
||||||
|
selected: ConditionSelected,
|
||||||
|
): AttributeInput => {
|
||||||
|
if (!selected.conditionValue) return { slug: attributeSlug };
|
||||||
|
|
||||||
const { label } = selected.conditionValue
|
const { label } = selected.conditionValue;
|
||||||
const { value } = selected
|
const { value } = selected;
|
||||||
|
|
||||||
if (label === "lower" && typeof value === "string") {
|
if (label === "lower" && typeof value === "string") {
|
||||||
return { slug: attributeSlug, valuesRange: { lte: parseFloat(value) } }
|
return { slug: attributeSlug, valuesRange: { lte: parseFloat(value) } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (label === "greater" && typeof value === "string") {
|
if (label === "greater" && typeof value === "string") {
|
||||||
return { slug: attributeSlug, valuesRange: { gte: parseFloat(value) } }
|
return { slug: attributeSlug, valuesRange: { gte: parseFloat(value) } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTuple(value) && label === "between") {
|
if (isTuple(value) && label === "between") {
|
||||||
const [lte, gte] = value
|
const [lte, gte] = value;
|
||||||
return { slug: attributeSlug, valuesRange: { lte: parseFloat(lte), gte: parseFloat(gte) } }
|
return {
|
||||||
|
slug: attributeSlug,
|
||||||
|
valuesRange: { lte: parseFloat(lte), gte: parseFloat(gte) },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isItemOption(value)) {
|
if (isItemOption(value)) {
|
||||||
return { slug: attributeSlug, values: [value.value] }
|
return { slug: attributeSlug, values: [value.value] };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isItemOptionArray(value)) {
|
if (isItemOptionArray(value)) {
|
||||||
return {slug: attributeSlug, values: value.map(x => x.value) }
|
return { slug: attributeSlug, values: value.map(x => x.value) };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return { slug: attributeSlug, values: [value] }
|
return { slug: attributeSlug, values: [value] };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return { slug: attributeSlug, values: value }
|
return { slug: attributeSlug, values: value };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === "true" || value === "false") {
|
if (value === "true" || value === "false") {
|
||||||
return { slug: attributeSlug, boolean: value }
|
return { slug: attributeSlug, boolean: value };
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return value;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
type ProductQueryVars = ProductWhereInput & { channel?: { eq: string } };
|
||||||
|
|
||||||
|
export const createProductQueryVariables = (
|
||||||
|
value: FilterContainer,
|
||||||
|
): ProductQueryVars => {
|
||||||
|
return value.reduce(
|
||||||
|
(p, c) => {
|
||||||
|
if (typeof c === "string" || Array.isArray(c)) return p;
|
||||||
|
|
||||||
export const createProductQueryVariables = (value: FilterContainer): ProductWhereInput => {
|
if (c.isStatic()) {
|
||||||
return value.reduce((p, c) => {
|
p[c.value.value as keyof ProductWhereInput] = createStaticQueryPart(
|
||||||
if (typeof c === "string" || Array.isArray(c)) return p
|
c.condition.selected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (c.isStatic()) {
|
if (c.isAttribute()) {
|
||||||
p[c.value.value as keyof ProductWhereInput] = createStaticQueryPart(c.condition.selected)
|
p.attributes!.push(
|
||||||
}
|
createAttributeQueryPart(c.value.value, c.condition.selected),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (c.isAttribute()) {
|
return p;
|
||||||
p.attributes!.push(
|
},
|
||||||
createAttributeQueryPart(c.value.value, c.condition.selected)
|
{ attributes: [] } as ProductWhereInput,
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
return p
|
|
||||||
}, { attributes: [] } as ProductWhereInput)
|
|
||||||
}
|
|
||||||
|
|
|
@ -290,6 +290,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
filterContainer: valueProvider.value,
|
filterContainer: valueProvider.value,
|
||||||
queryParams: params,
|
queryParams: params,
|
||||||
isChannelSelected: !!selectedChannel,
|
isChannelSelected: !!selectedChannel,
|
||||||
|
channelSlug: selectedChannel?.slug,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sort = getSortQueryVariables(params, !!selectedChannel);
|
const sort = getSortQueryVariables(params, !!selectedChannel);
|
||||||
|
@ -300,7 +301,6 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
...paginationState,
|
...paginationState,
|
||||||
...filterVariables,
|
...filterVariables,
|
||||||
sort,
|
sort,
|
||||||
channel: selectedChannel?.slug,
|
|
||||||
}),
|
}),
|
||||||
[params, settings.rowNumber, valueProvider.value],
|
[params, settings.rowNumber, valueProvider.value],
|
||||||
);
|
);
|
||||||
|
|
|
@ -61,6 +61,7 @@ import {
|
||||||
ProductListUrlQueryParams,
|
ProductListUrlQueryParams,
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
import { getProductGiftCardFilterParam } from "./utils";
|
import { getProductGiftCardFilterParam } from "./utils";
|
||||||
|
|
||||||
export const PRODUCT_FILTERS_KEY = "productPresets";
|
export const PRODUCT_FILTERS_KEY = "productPresets";
|
||||||
|
|
||||||
function getAttributeFilterParamType(inputType: AttributeInputTypeEnum) {
|
function getAttributeFilterParamType(inputType: AttributeInputTypeEnum) {
|
||||||
|
@ -496,16 +497,27 @@ export const getFilterVariables = ({
|
||||||
filterContainer,
|
filterContainer,
|
||||||
queryParams,
|
queryParams,
|
||||||
isChannelSelected,
|
isChannelSelected,
|
||||||
|
channelSlug,
|
||||||
}: {
|
}: {
|
||||||
isProductListingPageFiltersFlagEnabled: boolean;
|
isProductListingPageFiltersFlagEnabled: boolean;
|
||||||
filterContainer: FilterContainer;
|
filterContainer: FilterContainer;
|
||||||
queryParams: ProductListUrlFilters;
|
queryParams: ProductListUrlFilters;
|
||||||
isChannelSelected: boolean;
|
isChannelSelected: boolean;
|
||||||
|
channelSlug: string | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
if (isProductListingPageFiltersFlagEnabled) {
|
if (isProductListingPageFiltersFlagEnabled) {
|
||||||
const queryVars = createProductQueryVariables(filterContainer);
|
const queryVars = createProductQueryVariables(filterContainer);
|
||||||
return { where: queryVars, search: queryParams.query };
|
const { channel, ...where } = queryVars;
|
||||||
|
|
||||||
|
return {
|
||||||
|
where,
|
||||||
|
search: queryParams.query,
|
||||||
|
channel: channel?.eq,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { filter: getLegacyFilterVariables(queryParams, isChannelSelected) };
|
return {
|
||||||
|
filter: getLegacyFilterVariables(queryParams, isChannelSelected),
|
||||||
|
channel: channelSlug,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue