Experimental filters: add support for constrains on UI (#3969)

This commit is contained in:
Krzysztof Żuraw 2023-07-21 11:26:48 +02:00 committed by GitHub
parent 23bb5976c6
commit 5a6c255004
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1081 additions and 669 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---
Experimental filters: add support for constrains in MacawUI

1495
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,7 @@
"@material-ui/lab": "^4.0.0-alpha.61",
"@material-ui/styles": "^4.11.4",
"@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",
"@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6",

View file

@ -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";
export const LoadingFiltersArea = () => (
@ -11,16 +11,16 @@ export const LoadingFiltersArea = () => (
flexDirection="column"
>
<Box display="flex" flexDirection="column" gap={3} height="100%">
<_ExperimentalFilters.Skeleton height={7} />
<_ExperimentalFilters.Skeleton height={7} />
<_ExperimentalFilters.Skeleton height={7} />
<Skeleton height={7} />
<Skeleton height={7} />
<Skeleton height={7} />
</Box>
<Divider />
<Box display="flex" gap={4} justifyContent="space-between">
<_ExperimentalFilters.Skeleton height={7} __width="60px" />
<Skeleton height={7} __width="60px" />
<Box display="flex" gap={3}>
<_ExperimentalFilters.Skeleton height={7} __width="60px" />
<_ExperimentalFilters.Skeleton height={7} __width="60px" />
<Skeleton height={7} __width="60px" />
<Skeleton height={7} __width="60px" />
</Box>
</Box>
</Box>

View file

@ -17,7 +17,7 @@ export const STATIC_CONDITIONS = {
channel: [{ type: "select", label: "is", value: "input-5" }],
productType: [
{ 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" }],
isPublished: [{ type: "select", label: "is", value: "input-1" }],
@ -26,14 +26,13 @@ export const STATIC_CONDITIONS = {
giftCard: [{ type: "select", label: "is", value: "input-1" }],
};
export const CONSTRAINTS = {
channel: {
dependsOn: ["price", "isVisibleInListing"],
removable: true,
disabled: ["left", "condition"]
dependsOn: ["price", "isVisibleInListing", "isAvailable", "isPublished"],
removable: false,
disabled: ["left", "condition"],
},
}
};
export const STATIC_OPTIONS: LeftOperand[] = [
{ value: "price", label: "Price", type: "price", slug: "price" },
@ -45,12 +44,42 @@ export const STATIC_OPTIONS: LeftOperand[] = [
slug: "collection",
},
{ 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: "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" },
{
value: "productType",
label: "Product Type",
type: "productType",
slug: "productType",
},
{
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 = {
@ -69,38 +98,42 @@ export const ATTRIBUTE_INPUT_TYPE_CONDITIONS = {
};
export const getAtributeInputType = (item: ConditionItem | null) => {
const result = Object.entries(ATTRIBUTE_INPUT_TYPE_CONDITIONS)
.find(([_, value]) =>
value.find(entry => entry.type === item?.type && entry.label === item.label)
)
const result = Object.entries(ATTRIBUTE_INPUT_TYPE_CONDITIONS).find(
([_, value]) =>
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) => ({
label: "Yes",
value: "true",
slug: "true",
...({ type })
})
...{ type },
});
export const booleanOptionFalse = (type?: string) => ({
export const booleanOptionFalse = (type?: string) => ({
label: "No",
value: "false",
slug: "false",
...({ type })
})
...{ type },
});
export const createBooleanOptions = (type?: string): ItemOption[] => [
booleanOptionTrue(type),
booleanOptionFalse(type)
]
booleanOptionFalse(type),
];
export const createBoleanOption = (flag: boolean, type?: string): ItemOption => {
if (flag) return booleanOptionTrue(type)
export const createBoleanOption = (
flag: boolean,
type?: string,
): ItemOption => {
if (flag) return booleanOptionTrue(type);
return booleanOptionFalse(type)
}
return booleanOptionFalse(type);
};

View file

@ -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 { ConditionSelected } from "./FilterElement/ConditionSelected";
import { isItemOption, isItemOptionArray, isTuple } from "./FilterElement/ConditionValue";
import {
isItemOption,
isItemOptionArray,
isTuple,
} from "./FilterElement/ConditionValue";
type StaticQueryPart =
| string
| GlobalIdFilterInput
| boolean
| DecimalFilterInput
| DecimalFilterInput;
const createStaticQueryPart = (
selected: ConditionSelected,
): StaticQueryPart => {
if (!selected.conditionValue) return "";
const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => {
if (!selected.conditionValue) return ""
const { label } = selected.conditionValue
const { value } = selected
const { label } = selected.conditionValue;
const { value } = selected;
if (label === "lower") {
return { range: { lte: value } }
return { range: { lte: value } };
}
if (label === "greater") {
return { range: { gte: value } }
return { range: { gte: value } };
}
if (isTuple(value) && label === "between") {
const [lte, gte] = value
return { range: { lte, gte } }
const [lte, gte] = value;
return { range: { lte, gte } };
}
if (isItemOption(value)) {
return { eq: value.value }
return { eq: value.value };
}
if (isItemOptionArray(value)) {
return { oneOf: value.map(x => x.value) }
return { oneOf: value.map(x => x.value) };
}
if (typeof value === "string") {
return { eq: value }
return { eq: value };
}
if (Array.isArray(value)) {
return { eq: value }
return { eq: value };
}
return value
}
return value;
};
const createAttributeQueryPart = (attributeSlug: string, selected: ConditionSelected): AttributeInput => {
if (!selected.conditionValue) return { slug: attributeSlug }
const createAttributeQueryPart = (
attributeSlug: string,
selected: ConditionSelected,
): AttributeInput => {
if (!selected.conditionValue) return { slug: attributeSlug };
const { label } = selected.conditionValue
const { value } = selected
const { label } = selected.conditionValue;
const { value } = selected;
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") {
return { slug: attributeSlug, valuesRange: { gte: parseFloat(value) } }
return { slug: attributeSlug, valuesRange: { gte: parseFloat(value) } };
}
if (isTuple(value) && label === "between") {
const [lte, gte] = value
return { slug: attributeSlug, valuesRange: { lte: parseFloat(lte), gte: parseFloat(gte) } }
const [lte, gte] = value;
return {
slug: attributeSlug,
valuesRange: { lte: parseFloat(lte), gte: parseFloat(gte) },
};
}
if (isItemOption(value)) {
return { slug: attributeSlug, values: [value.value] }
return { slug: attributeSlug, values: [value.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") {
return { slug: attributeSlug, values: [value] }
return { slug: attributeSlug, values: [value] };
}
if (Array.isArray(value)) {
return { slug: attributeSlug, values: value }
return { slug: attributeSlug, values: value };
}
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 => {
return value.reduce((p, c) => {
if (typeof c === "string" || Array.isArray(c)) return p
if (c.isStatic()) {
p[c.value.value as keyof ProductWhereInput] = createStaticQueryPart(
c.condition.selected,
);
}
if (c.isStatic()) {
p[c.value.value as keyof ProductWhereInput] = createStaticQueryPart(c.condition.selected)
}
if (c.isAttribute()) {
p.attributes!.push(
createAttributeQueryPart(c.value.value, c.condition.selected),
);
}
if (c.isAttribute()) {
p.attributes!.push(
createAttributeQueryPart(c.value.value, c.condition.selected)
)
}
return p
}, { attributes: [] } as ProductWhereInput)
}
return p;
},
{ attributes: [] } as ProductWhereInput,
);
};

View file

@ -290,6 +290,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
filterContainer: valueProvider.value,
queryParams: params,
isChannelSelected: !!selectedChannel,
channelSlug: selectedChannel?.slug,
});
const sort = getSortQueryVariables(params, !!selectedChannel);
@ -300,7 +301,6 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
...paginationState,
...filterVariables,
sort,
channel: selectedChannel?.slug,
}),
[params, settings.rowNumber, valueProvider.value],
);

View file

@ -61,6 +61,7 @@ import {
ProductListUrlQueryParams,
} from "../../urls";
import { getProductGiftCardFilterParam } from "./utils";
export const PRODUCT_FILTERS_KEY = "productPresets";
function getAttributeFilterParamType(inputType: AttributeInputTypeEnum) {
@ -496,16 +497,27 @@ export const getFilterVariables = ({
filterContainer,
queryParams,
isChannelSelected,
channelSlug,
}: {
isProductListingPageFiltersFlagEnabled: boolean;
filterContainer: FilterContainer;
queryParams: ProductListUrlFilters;
isChannelSelected: boolean;
channelSlug: string | undefined;
}) => {
if (isProductListingPageFiltersFlagEnabled) {
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,
};
};