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/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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) => ({
|
||||
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);
|
||||
};
|
||||
|
|
|
@ -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): ProductWhereInput => {
|
||||
return value.reduce((p, c) => {
|
||||
if (typeof c === "string" || Array.isArray(c)) return p
|
||||
export const createProductQueryVariables = (
|
||||
value: FilterContainer,
|
||||
): ProductQueryVars => {
|
||||
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)
|
||||
p[c.value.value as keyof ProductWhereInput] = createStaticQueryPart(
|
||||
c.condition.selected,
|
||||
);
|
||||
}
|
||||
|
||||
if (c.isAttribute()) {
|
||||
p.attributes!.push(
|
||||
createAttributeQueryPart(c.value.value, c.condition.selected)
|
||||
)
|
||||
createAttributeQueryPart(c.value.value, c.condition.selected),
|
||||
);
|
||||
}
|
||||
|
||||
return p
|
||||
}, { attributes: [] } as ProductWhereInput)
|
||||
}
|
||||
return p;
|
||||
},
|
||||
{ attributes: [] } as ProductWhereInput,
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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],
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue