From 6abdc4613a54a7949f426fe0dc8c8ee0f4178544 Mon Sep 17 00:00:00 2001 From: Piotr Grundas Date: Thu, 29 Jul 2021 14:15:14 +0200 Subject: [PATCH 1/4] [SALEOR-3088] Date & DateTime attributes (#1180) --- CHANGELOG.md | 3 +- locale/defaultMessages.json | 15 + package.json | 2 +- schema.graphql | 11 +- .../AttributeDetails/AttributeDetails.tsx | 8 + .../components/AttributeDetails/messages.tsx | 8 + src/attributes/fixtures.ts | 148 ++- src/attributes/types/AttributeDetails.ts | 2 + src/attributes/types/AttributeValueCreate.ts | 2 + src/attributes/types/AttributeValueDelete.ts | 2 + src/attributes/types/AttributeValueUpdate.ts | 2 + src/attributes/utils/data.ts | 11 +- src/attributes/utils/handlers.test.ts | 20 +- src/attributes/utils/handlers.ts | 20 + .../views/AttributeCreate/AttributeCreate.tsx | 2 + src/components/Attributes/AttributeRow.tsx | 33 + .../Attributes/BasicAttributeRow.tsx | 29 +- src/components/Attributes/fixtures.ts | 105 ++- src/components/DateTimeField.tsx | 65 ++ .../Filter/FilterContent/FilterContent.tsx | 4 +- .../FilterContent/FilterContentBody.tsx | 136 +-- .../FilterContentBodyNameField.tsx | 63 ++ .../FilterContent/FilterDateTimeField.tsx | 149 ++++ .../FilterContent/FilterNumericField.tsx | 97 ++ .../FilterContent/FilterSingleSelectField.tsx | 48 + src/components/Filter/FilterContent/utils.ts | 58 +- .../MultiAutocompleteSelectField.tsx | 9 +- src/fragments/attributes.ts | 2 + src/fragments/products.ts | 2 + src/fragments/types/AttributeValueFragment.ts | 2 + .../types/AttributeValueListFragment.ts | 2 + src/fragments/types/PageAttributesFragment.ts | 6 + src/fragments/types/PageDetailsFragment.ts | 6 + src/fragments/types/Product.ts | 8 + src/fragments/types/ProductVariant.ts | 8 + .../types/ProductVariantAttributesFragment.ts | 8 + .../types/SelectedVariantAttributeFragment.ts | 4 + .../types/VariantAttributeFragment.ts | 2 + src/intl.ts | 7 + src/misc.ts | 5 + src/pages/fixtures.ts | 64 +- src/pages/types/PageDetails.ts | 6 + src/pages/types/PageType.ts | 2 + src/pages/types/PageUpdate.ts | 6 + src/productTypes/fixtures.ts | 140 ++- .../components/ProductListPage/filters.ts | 60 +- .../ProductVariantCreatorSummary.tsx | 18 +- .../ProductVariantCreatorValues.tsx | 142 ++- .../__snapshots__/reducer.test.ts.snap | 96 ++ .../createVariants.ts | 13 +- .../ProductVariantCreatorPage/fixtures.ts | 16 +- .../ProductVariantCreatorPage/form.ts | 2 +- .../ProductVariantCreatorPage/reducer.ts | 5 +- .../ProductVariantCreatorPage/utils.ts | 35 +- src/products/fixtures.ts | 168 +++- .../types/CreateMultipleVariantsData.ts | 8 + src/products/types/ProductDetails.ts | 8 + src/products/types/ProductList.ts | 2 + src/products/types/ProductType.ts | 2 + src/products/types/ProductUpdate.ts | 8 + .../types/ProductVariantCreateData.ts | 4 + src/products/types/ProductVariantDetails.ts | 8 + src/products/types/SimpleProductUpdate.ts | 40 + src/products/types/VariantCreate.ts | 8 + src/products/types/VariantUpdate.ts | 16 + src/products/utils/data.ts | 30 +- src/products/views/ProductList/filters.ts | 99 ++- src/searches/types/SearchAttributeValues.ts | 2 + .../__snapshots__/Stories.test.ts.snap | 840 ++++++++++++++++-- src/types/globalTypes.ts | 11 +- src/utils/filters/fields.ts | 15 + src/utils/filters/filters.ts | 2 +- 72 files changed, 2582 insertions(+), 408 deletions(-) create mode 100644 src/components/DateTimeField.tsx create mode 100644 src/components/Filter/FilterContent/FilterContentBodyNameField.tsx create mode 100644 src/components/Filter/FilterContent/FilterDateTimeField.tsx create mode 100644 src/components/Filter/FilterContent/FilterNumericField.tsx create mode 100644 src/components/Filter/FilterContent/FilterSingleSelectField.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index f187259f5d5..8a98d3c404d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,8 @@ All notable, unreleased changes to this project will be documented in this file. - Fix forbidden null sending as attribute value - #1201 by @orzechdev - Fix missing call for update metadata mutation - #1207 by @orzechdev - Disable next step when no value selected in variant selector - #1218 by @orzechdev +- Add boolean attributes - #1157 by @piotrgrundas +- Add date & date time attributes - #1180 by @piotrgrundas - Fix order links on home page - #1219 by @jwm0 - Fix incorrectly handled image upload errors - #1223 by @kamilpastuszka - Fix huge payload issue for plugins view - #1203 by @kamilpastuszka @@ -185,7 +187,6 @@ All notable, unreleased changes to this project will be documented in this file. - Update product stock management to newest design - #515 by @dominik-zeglen - Handle untracked products - #523 by @dominik-zeglen - Display correct error if there were no graphql errors - #525 by @dominik-zeglen -- Add boolean attributes - #1157 by @piotrgrundas ## 2.0.0 diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 31d21ebc760..a16e19be198 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -957,6 +957,14 @@ "context": "boolean attribute type", "string": "Boolean" }, + "src_dot_attributes_dot_components_dot_AttributeDetails_dot_date": { + "context": "date attribute type", + "string": "Date" + }, + "src_dot_attributes_dot_components_dot_AttributeDetails_dot_dateTime": { + "context": "date time attribute type", + "string": "Date Time" + }, "src_dot_attributes_dot_components_dot_AttributeDetails_dot_distance": { "context": "distance units type", "string": "Distance" @@ -2757,6 +2765,9 @@ "src_dot_dashboard": { "string": "Dashboard" }, + "src_dot_date": { + "string": "Date" + }, "src_dot_delete": { "context": "button", "string": "Delete" @@ -6653,6 +6664,10 @@ "src_dot_taxes_dot_views_dot_2411670026": { "string": "Successfully fetched tax rates" }, + "src_dot_time": { + "context": "independent of any particular day, eg. 11:35", + "string": "Time" + }, "src_dot_translations": { "context": "translations section name", "string": "Translations" diff --git a/package.json b/package.json index 05965e3e6db..d2590fb143c 100644 --- a/package.json +++ b/package.json @@ -254,7 +254,7 @@ "test:e2e:run": "start-server-and-test start http://localhost:9000 cy:run", "test:e2e:run:record": "start-server-and-test start http://localhost:9000 cy:run:record", "test:e2e:dev": "start-server-and-test start http://localhost:9000 cy:open", - "test": "jest src/", + "test": "TZ=UTC jest src/", "transpile-messages": "node scripts/transpile-tx.js", "lint": "npx eslint \"src/**/*.@(tsx|ts|jsx|js)\" --fix ; npx prettier --check \"src/**/*.@(tsx|ts|jsx|js)\" --write", "postbuild": "rimraf ./build/**/*.js.map", diff --git a/schema.graphql b/schema.graphql index 3b53598114f..4cfb6015512 100644 --- a/schema.graphql +++ b/schema.graphql @@ -518,6 +518,8 @@ input AttributeInput { slug: String! values: [String] valuesRange: IntRangeInput + dateTime: DateTimeRangeInput + date: DateRangeInput boolean: Boolean } @@ -529,6 +531,8 @@ enum AttributeInputTypeEnum { NUMERIC RICH_TEXT BOOLEAN + DATE + DATE_TIME } type AttributeReorderValues { @@ -610,6 +614,8 @@ type AttributeValue implements Node { file: File richText: JSONString boolean: Boolean + date: Date + dateTime: DateTime } type AttributeValueBulkDelete { @@ -661,6 +667,8 @@ input AttributeValueInput { references: [ID!] richText: JSONString boolean: Boolean + date: Date + dateTime: DateTime } type AttributeValueTranslatableContent implements Node { @@ -697,7 +705,8 @@ type AttributeValueUpdate { input BulkAttributeValueInput { id: ID - values: [String!]! + values: [String!] + boolean: Boolean } type BulkProductError { diff --git a/src/attributes/components/AttributeDetails/AttributeDetails.tsx b/src/attributes/components/AttributeDetails/AttributeDetails.tsx index d6653121bd4..85da11af37c 100644 --- a/src/attributes/components/AttributeDetails/AttributeDetails.tsx +++ b/src/attributes/components/AttributeDetails/AttributeDetails.tsx @@ -100,6 +100,14 @@ const AttributeDetails: React.FC = props => { { label: intl.formatMessage(inputTypeMessages.boolean), value: AttributeInputTypeEnum.BOOLEAN + }, + { + label: intl.formatMessage(inputTypeMessages.date), + value: AttributeInputTypeEnum.DATE + }, + { + label: intl.formatMessage(inputTypeMessages.dateTime), + value: AttributeInputTypeEnum.DATE_TIME } ]; const entityTypeChoices = [ diff --git a/src/attributes/components/AttributeDetails/messages.tsx b/src/attributes/components/AttributeDetails/messages.tsx index 02aa217d402..454ad896ff7 100644 --- a/src/attributes/components/AttributeDetails/messages.tsx +++ b/src/attributes/components/AttributeDetails/messages.tsx @@ -73,6 +73,14 @@ export const inputTypeMessages = defineMessages({ boolean: { defaultMessage: "Boolean", description: "boolean attribute type" + }, + date: { + defaultMessage: "Date", + description: "date attribute type" + }, + dateTime: { + defaultMessage: "Date Time", + description: "date time attribute type" } }); diff --git a/src/attributes/fixtures.ts b/src/attributes/fixtures.ts index 981d7d3c5dc..6745c6d0425 100644 --- a/src/attributes/fixtures.ts +++ b/src/attributes/fixtures.ts @@ -50,7 +50,9 @@ export const attribute: AttributeDetails_attribute = { reference: null, slug: "john-doe", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -64,7 +66,9 @@ export const attribute: AttributeDetails_attribute = { reference: null, slug: "milionare-pirate", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -108,7 +112,9 @@ export const attributes: Array value.slug); } diff --git a/src/attributes/utils/handlers.test.ts b/src/attributes/utils/handlers.test.ts index 99bd3c60526..fc4eff1e4b2 100644 --- a/src/attributes/utils/handlers.test.ts +++ b/src/attributes/utils/handlers.test.ts @@ -17,7 +17,9 @@ const attributes: FormsetData = [ reference: null, slug: "attr-1-v-1", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, @@ -38,7 +40,9 @@ const attributes: FormsetData = [ reference: null, slug: "attr-2-v-1", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null }, { __typename: "AttributeValue", @@ -48,7 +52,9 @@ const attributes: FormsetData = [ reference: null, slug: "attr-2-v-2", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null }, { __typename: "AttributeValue", @@ -58,7 +64,9 @@ const attributes: FormsetData = [ reference: null, slug: "attr-2-v-3", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, @@ -83,7 +91,9 @@ const attributes: FormsetData = [ reference: null, slug: "file-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, diff --git a/src/attributes/utils/handlers.ts b/src/attributes/utils/handlers.ts index ff16730a680..c2e9f5de85a 100644 --- a/src/attributes/utils/handlers.ts +++ b/src/attributes/utils/handlers.ts @@ -218,6 +218,20 @@ function getBooleanInput(attribute: AttributeInput) { }; } +function getDateInput(attribute: AttributeInput) { + return { + id: attribute.id, + date: attribute.value[0] + }; +} + +function getDateTimeInput(attribute: AttributeInput) { + return { + id: attribute.id, + dateTime: attribute.value[0] + }; +} + function getDefaultInput(attribute: AttributeInput) { return { id: attribute.id, @@ -245,6 +259,12 @@ export const prepareAttributesInput = ({ case AttributeInputTypeEnum.BOOLEAN: return getBooleanInput(attribute); + case AttributeInputTypeEnum.DATE: + return getDateInput(attribute); + + case AttributeInputTypeEnum.DATE_TIME: + return getDateTimeInput(attribute); + default: return getDefaultInput(attribute); } diff --git a/src/attributes/views/AttributeCreate/AttributeCreate.tsx b/src/attributes/views/AttributeCreate/AttributeCreate.tsx index e3560232d29..6d1c638ef3b 100644 --- a/src/attributes/views/AttributeCreate/AttributeCreate.tsx +++ b/src/attributes/views/AttributeCreate/AttributeCreate.tsx @@ -207,6 +207,8 @@ const AttributeDetails: React.FC = ({ params }) => { value: null, richText: null, boolean: null, + date: null, + dateTime: null, ...value } })) diff --git a/src/components/Attributes/AttributeRow.tsx b/src/components/Attributes/AttributeRow.tsx index dfd47fdac36..4f22bd4a4c5 100644 --- a/src/components/Attributes/AttributeRow.tsx +++ b/src/components/Attributes/AttributeRow.tsx @@ -15,6 +15,7 @@ import { getSingleDisplayValue } from "@saleor/components/Attributes/utils"; import Checkbox from "@saleor/components/Checkbox"; +import { DateTimeField } from "@saleor/components/DateTimeField"; import FileUploadField from "@saleor/components/FileUploadField"; import MultiAutocompleteSelectField from "@saleor/components/MultiAutocompleteSelectField"; import RichTextEditor from "@saleor/components/RichTextEditor"; @@ -24,6 +25,7 @@ import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFr import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment"; import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment"; import { FormsetChange } from "@saleor/hooks/useFormset"; +import { commonMessages } from "@saleor/intl"; import { FetchMoreProps, ReorderEvent } from "@saleor/types"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import React from "react"; @@ -219,6 +221,37 @@ const AttributeRow: React.FC = ({ ); + case AttributeInputTypeEnum.DATE: + return ( + + onChange(attribute.id, event.target.value)} + type="date" + value={attribute.value[0]} + InputLabelProps={{ shrink: true }} + /> + + ); + case AttributeInputTypeEnum.DATE_TIME: + return ( + + onChange(attribute.id, value)} + /> + + ); default: return ( diff --git a/src/components/Attributes/BasicAttributeRow.tsx b/src/components/Attributes/BasicAttributeRow.tsx index 6bc2a291747..e73d0761f7c 100644 --- a/src/components/Attributes/BasicAttributeRow.tsx +++ b/src/components/Attributes/BasicAttributeRow.tsx @@ -1,6 +1,7 @@ import { Typography } from "@material-ui/core"; import Grid from "@saleor/components/Grid"; import { makeStyles } from "@saleor/macaw-ui"; +import classNames from "classnames"; import React from "react"; const useStyles = makeStyles( @@ -15,6 +16,15 @@ const useStyles = makeStyles( attributeSectionLabel: { alignItems: "center", display: "flex" + }, + flex: { + columnGap: theme.spacing(2) + "px", + display: "flex", + flexDirection: "row", + [theme.breakpoints.down("md")]: { + flexDirection: "column", + rowGap: theme.spacing(2) + "px" + } } }), { name: "BasicAttributeRow" } @@ -22,11 +32,15 @@ const useStyles = makeStyles( interface BasicAttributeRowProps { label: string | React.ReactNode; + flexValueContainer?: boolean; } -const BasicAttributeRow: React.FC = props => { - const { label, children } = props; - const classes = useStyles(props); +const BasicAttributeRow: React.FC = ({ + label, + children, + flexValueContainer +}) => { + const classes = useStyles(); return ( @@ -36,7 +50,14 @@ const BasicAttributeRow: React.FC = props => { > {label} -
{children}
+
+ {children} +
); }; diff --git a/src/components/Attributes/fixtures.ts b/src/components/Attributes/fixtures.ts index 966f56dd179..a77050a7832 100644 --- a/src/components/Attributes/fixtures.ts +++ b/src/components/Attributes/fixtures.ts @@ -19,7 +19,9 @@ const DROPDOWN_ATTRIBUTE: AttributeInput = { reference: null, slug: "dropdown-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null }, { __typename: "AttributeValue", @@ -29,7 +31,9 @@ const DROPDOWN_ATTRIBUTE: AttributeInput = { reference: null, slug: "dropdown-second-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, @@ -51,7 +55,9 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = { reference: null, slug: "multiselect-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null }, { __typename: "AttributeValue", @@ -61,7 +67,9 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = { reference: null, slug: "multiselect-second-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null }, { __typename: "AttributeValue", @@ -71,7 +79,9 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = { reference: null, slug: "multiselect-third-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, @@ -97,7 +107,9 @@ const FILE_ATTRIBUTE: AttributeInput = { reference: null, slug: "file-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, @@ -134,7 +146,9 @@ const REFERENCE_ATTRIBUTE: AttributeInput = { reference: null, slug: "references-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null }, { __typename: "AttributeValue", @@ -144,7 +158,9 @@ const REFERENCE_ATTRIBUTE: AttributeInput = { reference: null, slug: "references-second-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null }, { __typename: "AttributeValue", @@ -154,7 +170,9 @@ const REFERENCE_ATTRIBUTE: AttributeInput = { reference: null, slug: "references-third-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, @@ -180,7 +198,9 @@ const RICH_TEXT_ATTRIBUTE: AttributeInput = { blocks: [{ data: { text: "Some cool text" }, type: "paragraph" }], version: "2.19.3" }), - boolean: null + boolean: null, + date: null, + dateTime: null } ], selectedValues: [] @@ -204,6 +224,8 @@ const NUMERIC_ATTRIBUTE: AttributeInput = { reference: null, richText: null, boolean: null, + date: null, + dateTime: null, slug: "319_35" } ] @@ -226,7 +248,9 @@ const BOOLEAN_ATTRIBUTE: AttributeInput = { reference: null, richText: null, boolean: true, - slug: "319_True" + slug: "319_True", + date: null, + dateTime: null } ] }, @@ -235,6 +259,53 @@ const BOOLEAN_ATTRIBUTE: AttributeInput = { value: [] }; +const DATE_ATTRIBUTE: AttributeInput = { + data: { + inputType: AttributeInputTypeEnum.DATE, + isRequired: true, + values: [ + { + __typename: "AttributeValue", + file: null, + id: "asdfasdfasdfasdf", + name: "Date Attribute: '2021-06-03 00:15:00+00:00'", + reference: null, + richText: null, + boolean: true, + slug: "319_True", + date: "2021-06-03", + dateTime: "2021-06-03 00:15:00+00:00" + } + ] + }, + id: "QXR0cmfsdfasfdjMasdfasdf1", + label: "Date Attribute", + value: [] +}; + +const DATE_TIME_ATTRIBUTE: AttributeInput = { + data: { + inputType: AttributeInputTypeEnum.DATE_TIME, + isRequired: true, + values: [ + { + __typename: "AttributeValue", + file: null, + id: "asdfasdfasdfasdf", + name: "Date Time Attribute: '2021-06-03 00:15:00+00:00'", + reference: null, + richText: null, + boolean: true, + slug: "319_True", + date: "2021-06-03", + dateTime: "2021-06-03 00:15:00+00:00" + } + ] + }, + id: "QXR0cmlidXasdfasdfasdf1", + label: "Date Time Attribute", + value: [] +}; export const ATTRIBUTES: AttributeInput[] = [ DROPDOWN_ATTRIBUTE, MULTISELECT_ATTRIBUTE, @@ -242,7 +313,9 @@ export const ATTRIBUTES: AttributeInput[] = [ REFERENCE_ATTRIBUTE, RICH_TEXT_ATTRIBUTE, NUMERIC_ATTRIBUTE, - BOOLEAN_ATTRIBUTE + BOOLEAN_ATTRIBUTE, + DATE_ATTRIBUTE, + DATE_TIME_ATTRIBUTE ]; export const ATTRIBUTES_SELECTED: AttributeInput[] = [ @@ -284,5 +357,13 @@ export const ATTRIBUTES_SELECTED: AttributeInput[] = [ { ...BOOLEAN_ATTRIBUTE, value: [JSON.stringify(BOOLEAN_ATTRIBUTE.data.values[0].boolean)] + }, + { + ...DATE_ATTRIBUTE, + value: [DATE_TIME_ATTRIBUTE.data.values[0].date] + }, + { + ...DATE_TIME_ATTRIBUTE, + value: [DATE_TIME_ATTRIBUTE.data.values[0].dateTime] } ]; diff --git a/src/components/DateTimeField.tsx b/src/components/DateTimeField.tsx new file mode 100644 index 00000000000..6aa0930b36f --- /dev/null +++ b/src/components/DateTimeField.tsx @@ -0,0 +1,65 @@ +import { TextField } from "@material-ui/core"; +import { TextFieldProps } from "@material-ui/core/TextField"; +import { getErrorMessage } from "@saleor/components/Attributes/utils"; +import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment"; +import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment"; +import { commonMessages } from "@saleor/intl"; +import { DateTime, joinDateTime, splitDateTime } from "@saleor/misc"; +import React, { useEffect, useState } from "react"; +import { useIntl } from "react-intl"; + +type DateTimeFieldProps = Omit & { + onChange: (value: string) => void; + error: ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment; + value: string; +}; + +export const DateTimeField: React.FC = ({ + disabled, + error, + name, + onChange, + value: initialValue +}) => { + const intl = useIntl(); + const [value, setValue] = useState( + initialValue ? splitDateTime(initialValue) : { date: "", time: "" } + ); + + useEffect(() => onChange(joinDateTime(value.date, value.time)), [value]); + + return ( + <> + { + const date = event.target.value; + setValue(value => ({ ...value, date })); + }} + type="date" + value={value.date} + InputLabelProps={{ shrink: true }} + /> + { + const time = event.target.value; + setValue(value => ({ ...value, time })); + }} + type="time" + value={value.time} + InputLabelProps={{ shrink: true }} + /> + + ); +}; diff --git a/src/components/Filter/FilterContent/FilterContent.tsx b/src/components/Filter/FilterContent/FilterContent.tsx index 6d63feff06d..7398d4c3f3b 100644 --- a/src/components/Filter/FilterContent/FilterContent.tsx +++ b/src/components/Filter/FilterContent/FilterContent.tsx @@ -21,9 +21,9 @@ import { InvalidFilters } from "../types"; import FilterContentBody, { FilterContentBodyProps } from "./FilterContentBody"; +import FilterContentBodyNameField from "./FilterContentBodyNameField"; import FilterContentHeader from "./FilterContentHeader"; import FilterErrorsList from "./FilterErrorsList"; -import FilterGroupLabel from "./FilterGroupLabel"; const useExpanderStyles = makeStyles( () => ({ @@ -219,7 +219,7 @@ const FilterContent: React.FC = ({ classes={summaryClasses} onClick={() => handleFilterOpen(filter)} > - handleFilterPropertyGroupChange(action, filter) diff --git a/src/components/Filter/FilterContent/FilterContentBody.tsx b/src/components/Filter/FilterContent/FilterContentBody.tsx index 9ccb2af0187..e4e03e3163e 100644 --- a/src/components/Filter/FilterContent/FilterContentBody.tsx +++ b/src/components/Filter/FilterContent/FilterContentBody.tsx @@ -1,26 +1,45 @@ -import { FormControlLabel, Radio } from "@material-ui/core"; -import FormSpacer from "@saleor/components/FormSpacer"; +import { FormControlLabel, Radio, TextField } from "@material-ui/core"; +import { fade } from "@material-ui/core/styles/colorManipulator"; +import { FilterDateTimeField } from "@saleor/components/Filter/FilterContent/FilterDateTimeField"; +import { FilterNumericField } from "@saleor/components/Filter/FilterContent/FilterNumericField"; +import { FilterSingleSelectField } from "@saleor/components/Filter/FilterContent/FilterSingleSelectField"; +import { useCommonStyles } from "@saleor/components/Filter/FilterContent/utils"; import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; -import SingleSelectField from "@saleor/components/SingleSelectField"; +import { makeStyles } from "@saleor/macaw-ui"; import classNames from "classnames"; import React from "react"; -import { useIntl } from "react-intl"; -import Arrow from "../Arrow"; import FilterAutocompleteField, { FilterAutocompleteDisplayValues } from "../FilterAutocompleteField"; import FilterOptionField from "../FilterOptionField"; import { FilterReducerAction } from "../reducer"; -import { FieldType, FilterType, IFilterElement } from "../types"; -import FilterRangeField from "./FilterRangeField"; -import FilterTextField from "./FilterTextField"; -import useStyles from "./styles"; -import { filterTestingContext, getIsFilterMultipleChoices } from "./utils"; +import { FieldType, IFilterElement } from "../types"; + +const useStyles = makeStyles( + theme => ({ + filterSettings: { + background: fade(theme.palette.primary.main, 0.1), + padding: theme.spacing(2, 3) + }, + + option: { + left: -theme.spacing(0.5), + position: "relative" + }, + optionRadio: { + left: -theme.spacing(0.25) + } + }), + { name: "FilterContentBody" } +); + +const filterTestingContext = "filter-field"; export interface FilterContentBodyProps { + children?: React.ReactNode; filter: IFilterElement; - currencySymbol: string; + currencySymbol?: string; initialAutocompleteDisplayValues: FilterAutocompleteDisplayValues; onFilterPropertyChange: React.Dispatch>; autocompleteDisplayValues: FilterAutocompleteDisplayValues; @@ -38,65 +57,66 @@ const FilterContentBody: React.FC = ({ setAutocompleteDisplayValues, initialAutocompleteDisplayValues }) => { - const intl = useIntl(); const classes = useStyles({}); + const commonClasses = useCommonStyles({}); + + const isDateField = [FieldType.date, FieldType.dateTime].includes( + filter.type + ); + const isNumericField = [FieldType.price, FieldType.number].includes( + filter.type + ); return (
{children} {filter.type === FieldType.text && ( - + onFilterPropertyChange({ + payload: { + name: filter.name, + update: { + value: [event.target.value, filter.value[1]] + } + }, + type: "set-property" + }) + } /> )} - {[FieldType.date, FieldType.price, FieldType.number].includes( - filter.type - ) && ( + {isDateField && ( <> - - onFilterPropertyChange({ - payload: { - name: filter.name, - update: { - multiple: event.target.value === FilterType.MULTIPLE - } - }, - type: "set-property" - }) - } + + - -
-
- -
- {filter.multiple ? ( - - ) : ( - - )} -
)} + {isNumericField && ( + <> + + + + )} + {filter.type === FieldType.options && ( ({ + container: { + "&:not(:last-of-type)": { + borderBottom: `1px solid ${theme.palette.divider}` + }, + padding: theme.spacing(1, 2.5) + } + }), + { name: "FilterContentBodyNameField" } +); + +export interface FilterContentBodyNameFieldProps { + filter: IFilterElement; + onFilterPropertyChange: React.Dispatch>; +} + +const FilterContentBodyNameField: React.FC = ({ + filter, + onFilterPropertyChange +}) => { + const classes = useStyles({}); + + if (!filter) { + return null; + } + + return ( +
+ + } + label={filter.label} + onClick={event => event.stopPropagation()} + onChange={() => + onFilterPropertyChange({ + payload: { + name: filter.name, + update: { + active: !filter.active + } + }, + type: "set-property" + }) + } + /> +
+ ); +}; + +export default FilterContentBodyNameField; diff --git a/src/components/Filter/FilterContent/FilterDateTimeField.tsx b/src/components/Filter/FilterContent/FilterDateTimeField.tsx new file mode 100644 index 00000000000..3fdd78eac86 --- /dev/null +++ b/src/components/Filter/FilterContent/FilterDateTimeField.tsx @@ -0,0 +1,149 @@ +import { TextField } from "@material-ui/core"; +import { FieldType } from "@saleor/components/Filter"; +import Arrow from "@saleor/components/Filter/Arrow"; +import { splitDateTime } from "@saleor/misc"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { + FilterFieldBaseProps, + filterTestingContext, + getDateFilterValue, + getDateTimeFilterValue, + useCommonStyles +} from "./utils"; + +type FilterDateTimeFieldProps = FilterFieldBaseProps; + +export const FilterDateTimeField: React.FC = ({ + filter, + onFilterPropertyChange +}) => { + const classes = useCommonStyles({}); + const isDateTime = filter.type === FieldType.dateTime; + const isMultiple = filter.multiple; + + const handleChange = (value: string[]) => + onFilterPropertyChange({ + payload: { + name: filter.name, + update: { + value + } + }, + type: "set-property" + }); + + return ( + <> +
+
+ +
+ { + const value = getDateFilterValue( + event.target.value, + filter.value[0], + isDateTime + ); + handleChange(isMultiple ? [value, filter.value[1]] : [value]); + }} + /> + {isDateTime && ( + { + const value = getDateTimeFilterValue( + filter.value[0], + event.target.value + ); + handleChange(isMultiple ? [value, filter.value[1]] : [value]); + }} + /> + )} +
+ {filter.multiple && ( + <> +
+
+ + + +
+
+
+ + handleChange([ + filter.value[0], + getDateFilterValue( + event.target.value, + filter.value[1], + isDateTime + ) + ]) + } + /> + {isDateTime && ( + + handleChange([ + filter.value[0], + getDateTimeFilterValue(filter.value[1], event.target.value) + ]) + } + /> + )} +
+ + )} + + ); +}; diff --git a/src/components/Filter/FilterContent/FilterNumericField.tsx b/src/components/Filter/FilterContent/FilterNumericField.tsx new file mode 100644 index 00000000000..17013f87a3f --- /dev/null +++ b/src/components/Filter/FilterContent/FilterNumericField.tsx @@ -0,0 +1,97 @@ +import { TextField } from "@material-ui/core"; +import { FieldType } from "@saleor/components/Filter"; +import Arrow from "@saleor/components/Filter/Arrow"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { + FilterFieldBaseProps, + filterTestingContext, + useCommonStyles +} from "./utils"; + +type FilterNumericFieldProps = FilterFieldBaseProps & { + currencySymbol: string | undefined; +}; + +export const FilterNumericField: React.FC = ({ + filter, + onFilterPropertyChange, + currencySymbol +}) => { + const classes = useCommonStyles({}); + const isMultiple = filter.multiple; + + const handleChange = (value: string[]) => + onFilterPropertyChange({ + payload: { + name: filter.name, + update: { + value + } + }, + type: "set-property" + }); + + return ( + <> +
+
+ +
+ + handleChange(isMultiple ? [value, filter.value[1]] : [value]) + } + /> +
+ {filter.multiple && ( + <> +
+
+ + + +
+
+
+ + handleChange([filter.value[0], event.target.value]) + } + /> +
+ + )} + + ); +}; diff --git a/src/components/Filter/FilterContent/FilterSingleSelectField.tsx b/src/components/Filter/FilterContent/FilterSingleSelectField.tsx new file mode 100644 index 00000000000..6545832f6c6 --- /dev/null +++ b/src/components/Filter/FilterContent/FilterSingleSelectField.tsx @@ -0,0 +1,48 @@ +import { FilterType } from "@saleor/components/Filter"; +import { + FilterFieldBaseProps, + getIsFilterMultipleChoices, + useCommonStyles +} from "@saleor/components/Filter/FilterContent/utils"; +import FormSpacer from "@saleor/components/FormSpacer"; +import React from "react"; +import { useIntl } from "react-intl"; + +import SingleSelectField from "../../SingleSelectField/SingleSelectField"; + +type FilterSingleSelectFieldProps = FilterFieldBaseProps; + +export const FilterSingleSelectField: React.FC = ({ + filter, + onFilterPropertyChange +}) => { + const classes = useCommonStyles({}); + const intl = useIntl(); + + return ( + <> + + onFilterPropertyChange({ + payload: { + name: filter.name, + update: { + multiple: event.target.value === FilterType.MULTIPLE + } + }, + type: "set-property" + }) + } + /> + + + ); +}; diff --git a/src/components/Filter/FilterContent/utils.ts b/src/components/Filter/FilterContent/utils.ts index 1fc1a741f06..48cdcaa82c3 100644 --- a/src/components/Filter/FilterContent/utils.ts +++ b/src/components/Filter/FilterContent/utils.ts @@ -1,10 +1,45 @@ +import { FilterReducerAction } from "@saleor/components/Filter/reducer"; import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField"; +import { makeStyles } from "@saleor/macaw-ui"; +import { joinDateTime, splitDateTime } from "@saleor/misc"; +import React from "react"; import { IntlShape } from "react-intl"; -import { FilterType } from "../types"; +import { FilterType, IFilterElement } from "../types"; export const filterTestingContext = "filter-field"; +export interface FilterFieldBaseProps { + filter: IFilterElement; + onFilterPropertyChange: React.Dispatch>; +} + +export const useCommonStyles = makeStyles( + theme => ({ + andLabel: { + margin: theme.spacing(1, 2, 1, 0) + }, + arrow: { + marginRight: theme.spacing(2) + }, + input: { + padding: "12px 0 9px 12px" + }, + inputRange: { + alignItems: "center", + display: "flex" + }, + inputTime: { + marginLeft: theme.spacing(1), + width: "150px" + }, + spacer: { + paddingRight: theme.spacing(4) + } + }), + { name: "FilterContentBodyCommon" } +); + export function getIsFilterMultipleChoices( intl: IntlShape ): SingleAutocompleteChoiceType[] { @@ -27,3 +62,24 @@ export function getIsFilterMultipleChoices( } ]; } + +export const getDateFilterValue = ( + dateTime: string, + dateTimeString: string | null, + dateTimeFormat: boolean +) => { + const { date } = splitDateTime(dateTime); + if (!dateTimeFormat) { + return date; + } + const { time } = splitDateTime(dateTimeString); + return joinDateTime(date, time); +}; + +export const getDateTimeFilterValue = ( + dateTimeString: string | null, + timeString: string +) => { + const { date } = splitDateTime(dateTimeString || new Date().toISOString()); + return joinDateTime(date, timeString); +}; diff --git a/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.tsx b/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.tsx index a90617612e3..7180f4ccc85 100644 --- a/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.tsx +++ b/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectField.tsx @@ -63,6 +63,10 @@ const useStyles = makeStyles( margin: theme.spacing(1, 0), paddingLeft: theme.spacing(2), paddingRight: theme.spacing(1) + }, + adornment: { + display: "flex", + alignItems: "center" } }), { name: "MultiAutocompleteSelectField" } @@ -86,6 +90,7 @@ export interface MultiAutocompleteSelectFieldProps fetchChoices?: (value: string) => void; onChange: (event: React.ChangeEvent) => void; fetchOnFocus?: boolean; + endAdornment?: React.ReactNode; } const DebounceAutocomplete: React.ComponentType +
+ {endAdornment} toggleMenu()} />
), diff --git a/src/fragments/attributes.ts b/src/fragments/attributes.ts index e409a0f99d6..1a4ab276e65 100644 --- a/src/fragments/attributes.ts +++ b/src/fragments/attributes.ts @@ -16,6 +16,8 @@ export const attributeValueFragment = gql` reference richText boolean + date + dateTime } `; diff --git a/src/fragments/products.ts b/src/fragments/products.ts index b8138c007a9..6ed99808067 100644 --- a/src/fragments/products.ts +++ b/src/fragments/products.ts @@ -150,6 +150,8 @@ export const productVariantAttributesFragment = gql` variantAttributes(variantSelection: VARIANT_SELECTION) { id name + inputType + unit choices( first: $firstValues after: $afterValues diff --git a/src/fragments/types/AttributeValueFragment.ts b/src/fragments/types/AttributeValueFragment.ts index bb0841b86cb..b16f4005083 100644 --- a/src/fragments/types/AttributeValueFragment.ts +++ b/src/fragments/types/AttributeValueFragment.ts @@ -22,4 +22,6 @@ export interface AttributeValueFragment { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } diff --git a/src/fragments/types/AttributeValueListFragment.ts b/src/fragments/types/AttributeValueListFragment.ts index 320b7960961..02f69d59f28 100644 --- a/src/fragments/types/AttributeValueListFragment.ts +++ b/src/fragments/types/AttributeValueListFragment.ts @@ -30,6 +30,8 @@ export interface AttributeValueListFragment_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface AttributeValueListFragment_edges { diff --git a/src/fragments/types/PageAttributesFragment.ts b/src/fragments/types/PageAttributesFragment.ts index c4bf17bb2d1..41ef483254a 100644 --- a/src/fragments/types/PageAttributesFragment.ts +++ b/src/fragments/types/PageAttributesFragment.ts @@ -32,6 +32,8 @@ export interface PageAttributesFragment_attributes_attribute_choices_edges_node reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageAttributesFragment_attributes_attribute_choices_edges { @@ -73,6 +75,8 @@ export interface PageAttributesFragment_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageAttributesFragment_attributes { @@ -104,6 +108,8 @@ export interface PageAttributesFragment_pageType_attributes_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageAttributesFragment_pageType_attributes_choices_edges { diff --git a/src/fragments/types/PageDetailsFragment.ts b/src/fragments/types/PageDetailsFragment.ts index 3ba8f2171a6..313a8b5bfc8 100644 --- a/src/fragments/types/PageDetailsFragment.ts +++ b/src/fragments/types/PageDetailsFragment.ts @@ -32,6 +32,8 @@ export interface PageDetailsFragment_attributes_attribute_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageDetailsFragment_attributes_attribute_choices_edges { @@ -73,6 +75,8 @@ export interface PageDetailsFragment_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageDetailsFragment_attributes { @@ -104,6 +108,8 @@ export interface PageDetailsFragment_pageType_attributes_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageDetailsFragment_pageType_attributes_choices_edges { diff --git a/src/fragments/types/Product.ts b/src/fragments/types/Product.ts index 575c8dc1b78..1f0eaa127b8 100644 --- a/src/fragments/types/Product.ts +++ b/src/fragments/types/Product.ts @@ -32,6 +32,8 @@ export interface Product_attributes_attribute_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface Product_attributes_attribute_choices_edges { @@ -73,6 +75,8 @@ export interface Product_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface Product_attributes { @@ -104,6 +108,8 @@ export interface Product_productType_variantAttributes_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface Product_productType_variantAttributes_choices_edges { @@ -122,6 +128,8 @@ export interface Product_productType_variantAttributes { __typename: "Attribute"; id: string; name: string | null; + inputType: AttributeInputTypeEnum | null; + unit: MeasurementUnitsEnum | null; choices: Product_productType_variantAttributes_choices | null; } diff --git a/src/fragments/types/ProductVariant.ts b/src/fragments/types/ProductVariant.ts index 70afef82689..bcd18ab3d45 100644 --- a/src/fragments/types/ProductVariant.ts +++ b/src/fragments/types/ProductVariant.ts @@ -44,6 +44,8 @@ export interface ProductVariant_selectionAttributes_attribute_choices_edges_node reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariant_selectionAttributes_attribute_choices_edges { @@ -85,6 +87,8 @@ export interface ProductVariant_selectionAttributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariant_selectionAttributes { @@ -116,6 +120,8 @@ export interface ProductVariant_nonSelectionAttributes_attribute_choices_edges_n reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariant_nonSelectionAttributes_attribute_choices_edges { @@ -157,6 +163,8 @@ export interface ProductVariant_nonSelectionAttributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariant_nonSelectionAttributes { diff --git a/src/fragments/types/ProductVariantAttributesFragment.ts b/src/fragments/types/ProductVariantAttributesFragment.ts index b09c02789e8..cc79bcd95e1 100644 --- a/src/fragments/types/ProductVariantAttributesFragment.ts +++ b/src/fragments/types/ProductVariantAttributesFragment.ts @@ -32,6 +32,8 @@ export interface ProductVariantAttributesFragment_attributes_attribute_choices_e reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantAttributesFragment_attributes_attribute_choices_edges { @@ -73,6 +75,8 @@ export interface ProductVariantAttributesFragment_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantAttributesFragment_attributes { @@ -104,6 +108,8 @@ export interface ProductVariantAttributesFragment_productType_variantAttributes_ reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantAttributesFragment_productType_variantAttributes_choices_edges { @@ -122,6 +128,8 @@ export interface ProductVariantAttributesFragment_productType_variantAttributes __typename: "Attribute"; id: string; name: string | null; + inputType: AttributeInputTypeEnum | null; + unit: MeasurementUnitsEnum | null; choices: ProductVariantAttributesFragment_productType_variantAttributes_choices | null; } diff --git a/src/fragments/types/SelectedVariantAttributeFragment.ts b/src/fragments/types/SelectedVariantAttributeFragment.ts index bdf0efb3078..f56c418b87d 100644 --- a/src/fragments/types/SelectedVariantAttributeFragment.ts +++ b/src/fragments/types/SelectedVariantAttributeFragment.ts @@ -32,6 +32,8 @@ export interface SelectedVariantAttributeFragment_attribute_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SelectedVariantAttributeFragment_attribute_choices_edges { @@ -73,6 +75,8 @@ export interface SelectedVariantAttributeFragment_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SelectedVariantAttributeFragment { diff --git a/src/fragments/types/VariantAttributeFragment.ts b/src/fragments/types/VariantAttributeFragment.ts index 4b32765a6e7..bbdf5d3a05f 100644 --- a/src/fragments/types/VariantAttributeFragment.ts +++ b/src/fragments/types/VariantAttributeFragment.ts @@ -32,6 +32,8 @@ export interface VariantAttributeFragment_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantAttributeFragment_choices_edges { diff --git a/src/intl.ts b/src/intl.ts index 03ed4b49679..fb66dd9e88a 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -108,6 +108,13 @@ export const commonMessages = defineMessages({ }, yes: { defaultMessage: "Yes" + }, + date: { + defaultMessage: "Date" + }, + time: { + defaultMessage: "Time", + description: "independent of any particular day, eg. 11:35" } }); diff --git a/src/misc.ts b/src/misc.ts index e1c9a28d7bb..08de30e3133 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -346,6 +346,11 @@ export function stopPropagation(cb: (event?: AnyEvent) => void) { }; } +export interface DateTime { + date: string; + time: string; +} + export function joinDateTime(date: string, time?: string) { if (!date) { return null; diff --git a/src/pages/fixtures.ts b/src/pages/fixtures.ts index 6cfb40444ef..b5e8de2766d 100644 --- a/src/pages/fixtures.ts +++ b/src/pages/fixtures.ts @@ -70,7 +70,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -84,7 +86,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -98,7 +102,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -114,7 +120,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ], __typename: "SelectedAttribute" @@ -149,7 +157,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -163,7 +173,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -177,7 +189,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -191,7 +205,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -207,7 +223,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ], __typename: "SelectedAttribute" @@ -255,7 +273,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -269,7 +289,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -283,7 +305,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -317,7 +341,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -331,7 +357,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -345,7 +373,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -359,7 +389,9 @@ export const page: PageDetails_page = { __typename: "AttributeValue", file: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] diff --git a/src/pages/types/PageDetails.ts b/src/pages/types/PageDetails.ts index 5fa3190ece7..6d8f71b4926 100644 --- a/src/pages/types/PageDetails.ts +++ b/src/pages/types/PageDetails.ts @@ -32,6 +32,8 @@ export interface PageDetails_page_attributes_attribute_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageDetails_page_attributes_attribute_choices_edges { @@ -73,6 +75,8 @@ export interface PageDetails_page_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageDetails_page_attributes { @@ -104,6 +108,8 @@ export interface PageDetails_page_pageType_attributes_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageDetails_page_pageType_attributes_choices_edges { diff --git a/src/pages/types/PageType.ts b/src/pages/types/PageType.ts index 3db36642051..3b322fffebe 100644 --- a/src/pages/types/PageType.ts +++ b/src/pages/types/PageType.ts @@ -32,6 +32,8 @@ export interface PageType_pageType_attributes_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageType_pageType_attributes_choices_edges { diff --git a/src/pages/types/PageUpdate.ts b/src/pages/types/PageUpdate.ts index 616217ff6ba..394e579e7dd 100644 --- a/src/pages/types/PageUpdate.ts +++ b/src/pages/types/PageUpdate.ts @@ -39,6 +39,8 @@ export interface PageUpdate_pageUpdate_page_attributes_attribute_choices_edges_n reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageUpdate_pageUpdate_page_attributes_attribute_choices_edges { @@ -80,6 +82,8 @@ export interface PageUpdate_pageUpdate_page_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageUpdate_pageUpdate_page_attributes { @@ -111,6 +115,8 @@ export interface PageUpdate_pageUpdate_page_pageType_attributes_choices_edges_no reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface PageUpdate_pageUpdate_page_pageType_attributes_choices_edges { diff --git a/src/productTypes/fixtures.ts b/src/productTypes/fixtures.ts index 9a9e9925247..e6e7e3338ba 100644 --- a/src/productTypes/fixtures.ts +++ b/src/productTypes/fixtures.ts @@ -47,7 +47,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -64,7 +66,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -105,7 +109,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -122,7 +128,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -139,7 +147,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -156,7 +166,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -197,7 +209,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -238,7 +252,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -255,7 +271,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -272,7 +290,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -313,7 +333,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -330,7 +352,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -371,7 +395,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -388,7 +414,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -405,7 +433,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -446,7 +476,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -463,7 +495,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -504,7 +538,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -521,7 +557,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -538,7 +576,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -555,7 +595,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -572,7 +614,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -589,7 +633,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -630,7 +676,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -647,7 +695,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -688,7 +738,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -705,7 +757,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -746,7 +800,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -763,7 +819,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -804,7 +862,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -821,7 +881,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -838,7 +900,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -855,7 +919,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -872,7 +938,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -889,7 +957,9 @@ export const attributes: ProductType_productType_productAttributes[] = [ type: "STRING", value: "", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] diff --git a/src/products/components/ProductListPage/filters.ts b/src/products/components/ProductListPage/filters.ts index 3b7462f7c20..03c5f4df74c 100644 --- a/src/products/components/ProductListPage/filters.ts +++ b/src/products/components/ProductListPage/filters.ts @@ -9,6 +9,8 @@ import { import { createAutocompleteField, createBooleanField, + createDateField, + createDateTimeField, createOptionsField, createPriceField } from "@saleor/utils/filters/fields"; @@ -24,15 +26,15 @@ export enum ProductFilterKeys { channel = "channel" } +export type AttributeFilterOpts = FilterOpts & { + id: string; + name: string; + slug: string; + inputType: AttributeInputTypeEnum; +}; + export interface ProductListFilterOpts { - attributes: Array< - FilterOpts & { - id: string; - name: string; - slug: string; - inputType: AttributeInputTypeEnum; - } - >; + attributes: AttributeFilterOpts[]; attributeChoices: FilterOpts & AutocompleteFilterOpts; categories: FilterOpts & AutocompleteFilterOpts; collections: FilterOpts & AutocompleteFilterOpts; @@ -76,15 +78,33 @@ const messages = defineMessages({ } }); +const filterByType = (type: AttributeInputTypeEnum) => ( + attribute: AttributeFilterOpts +) => attribute.inputType === type; + export function createFilterStructure( intl: IntlShape, opts: ProductListFilterOpts ): IFilter { - const booleanAttributes = opts.attributes.filter( - ({ inputType }) => inputType === AttributeInputTypeEnum.BOOLEAN + const attributes = opts.attributes; + + const booleanAttributes = attributes.filter( + filterByType(AttributeInputTypeEnum.BOOLEAN) + ); + const dateAttributes = attributes.filter( + filterByType(AttributeInputTypeEnum.DATE) ); + const dateTimeAttributes = attributes.filter( + filterByType(AttributeInputTypeEnum.DATE_TIME) + ); + const defaultAttributes = opts.attributes.filter( - ({ inputType }) => !inputType.includes(AttributeInputTypeEnum.BOOLEAN) + ({ inputType }) => + ![ + AttributeInputTypeEnum.BOOLEAN, + AttributeInputTypeEnum.DATE, + AttributeInputTypeEnum.DATE_TIME + ].includes(inputType) ); return [ @@ -195,9 +215,25 @@ export function createFilterStructure( active: attr.active, group: ProductFilterKeys.attributes })), + ...dateAttributes.map(attr => ({ + ...createDateField(attr.slug, attr.name, { + min: attr.value[0], + max: attr.value[1] ?? attr.value[0] + }), + active: attr.active, + group: ProductFilterKeys.attributes + })), + ...dateTimeAttributes.map(attr => ({ + ...createDateTimeField(attr.slug, attr.name, { + min: attr.value[0], + max: attr.value[1] ?? attr.value[0] + }), + active: attr.active, + group: ProductFilterKeys.attributes + })), ...defaultAttributes.map(attr => ({ ...createAutocompleteField( - attr.slug as any, + attr.slug, attr.name, attr.value, opts.attributeChoices.displayValues, diff --git a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorSummary.tsx b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorSummary.tsx index d0ce0f50479..3d8edfce092 100644 --- a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorSummary.tsx +++ b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorSummary.tsx @@ -118,13 +118,15 @@ function getVariantName( return attributes.reduce( (acc, attribute) => [ ...acc, - attribute.values?.find( - value => - value?.slug === - variant.attributes.find( - variantAttribute => variantAttribute.id === attribute.id - ).values[0] - )?.value?.name + attribute.values?.find(value => { + const variantAttributeValue = variant.attributes.find( + variantAttribute => variantAttribute.id === attribute.id + ); + return ( + variantAttributeValue.values?.[0] === value?.slug || + variantAttributeValue.boolean === value.value.boolean + ); + })?.value?.name ], [] ); @@ -215,7 +217,7 @@ const ProductVariantCreatorSummary: React.FC return ( attribute.values[0]) + .map(attribute => attribute.values?.[0] ?? attribute.boolean) .join(":")} >
diff --git a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorValues.tsx b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorValues.tsx index 48fd522e884..7aabb0b1e49 100644 --- a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorValues.tsx +++ b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorValues.tsx @@ -1,4 +1,5 @@ import { Card, CardContent } from "@material-ui/core"; +import { getMeasurementUnitMessage } from "@saleor/attributes/components/AttributeDetails/utils"; import { getMultiChoices } from "@saleor/components/Attributes/utils"; import CardSpacer from "@saleor/components/CardSpacer"; import CardTitle from "@saleor/components/CardTitle"; @@ -6,12 +7,23 @@ import LimitReachedAlert from "@saleor/components/LimitReachedAlert"; import MultiAutocompleteSelectField from "@saleor/components/MultiAutocompleteSelectField"; import Skeleton from "@saleor/components/Skeleton"; import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFragment"; +import { commonMessages } from "@saleor/intl"; import { getById } from "@saleor/orders/components/OrderReturnPage/utils"; +import { + getBasicAttributeValue, + getBooleanAttributeValue +} from "@saleor/products/components/ProductVariantCreatorPage/utils"; import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; import { SearchAttributeValues_attribute_choices_edges_node } from "@saleor/searches/types/SearchAttributeValues"; import { FetchMoreProps } from "@saleor/types"; +import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import React from "react"; -import { defineMessages, FormattedMessage, useIntl } from "react-intl"; +import { + defineMessages, + FormattedMessage, + IntlShape, + useIntl +} from "react-intl"; import { Attribute, @@ -52,6 +64,33 @@ export function getMultiDisplayValues( })); } +const getBooleanDisplayValues = ( + intl: IntlShape, + values: Array>> +) => { + if (!values.length) { + return []; + } + + const choices = getBooleanChoices(intl); + return values.map(({ value: { boolean } }) => + choices.find(({ value }) => value === boolean) + ); +}; + +const getBooleanChoices = ( + intl: IntlShape, + values?: Array>> +) => { + const selectedValues = values?.map(({ value }) => value.boolean) ?? []; + const choices = [ + { label: intl.formatMessage(commonMessages.yes), value: true }, + { label: intl.formatMessage(commonMessages.no), value: false } + ]; + + return choices.filter(({ value }) => !selectedValues.includes(value)); +}; + export interface ProductVariantCreatorValuesProps { attributes: ProductDetails_product_productType_variantAttributes[]; attributeValues: SearchAttributeValues_attribute_choices_edges_node[]; @@ -61,7 +100,7 @@ export interface ProductVariantCreatorValuesProps { variantsLeft: number | null; onValueClick: ( attributeId: string, - value: AttributeValue + value: AttributeValue> ) => void; } @@ -78,16 +117,22 @@ const ProductVariantCreatorValues: React.FC = const intl = useIntl(); const variantsNumber = getVariantsNumber(data); - const handleValueClick = (attributeId: string, valueSlug: string) => { - const dataAttribute = data.attributes.find(getById(attributeId)); - - onValueClick(attributeId, { - slug: valueSlug, - value: - dataAttribute?.values.find(value => value.slug === valueSlug)?.value || - attributeValues.find(value => value.slug === valueSlug) - }); - }; + const handleValueClick = ( + attributeId: string, + attributeName: string, + attributeValue: string | boolean + ) => + onValueClick( + attributeId, + typeof attributeValue === "boolean" + ? getBooleanAttributeValue(attributeName, attributeValue) + : getBasicAttributeValue( + attributeId, + attributeValue, + attributeValues, + data + ) + ); return ( <> @@ -112,25 +157,60 @@ const ProductVariantCreatorValues: React.FC = } /> - - handleValueClick(attribute.id, event.target.value) - } - allowCustomValues={true} - fetchOnFocus={true} - fetchChoices={value => - fetchAttributeValues(value, attribute.id) - } - {...fetchMoreAttributeValues} - /> + {attribute.inputType === AttributeInputTypeEnum.BOOLEAN ? ( + id === attribute.id).values + )} + name={`attribute:${attribute.name}`} + label={intl.formatMessage(messages.multipleValueLabel)} + value={getMultiValues(data.attributes, attribute)} + onChange={event => + handleValueClick( + attribute.id, + attribute.name, + event.target.value + ) + } + allowCustomValues={false} + choices={getBooleanChoices( + intl, + data.attributes.find(({ id }) => id === attribute.id).values + )} + /> + ) : ( + + handleValueClick( + attribute.id, + attribute.name, + event.target.value + ) + } + endAdornment={ + attribute.unit && + getMeasurementUnitMessage( + attribute.unit, + intl.formatMessage + ) + } + fetchOnFocus={true} + allowCustomValues={true} + fetchChoices={value => + fetchAttributeValues(value, attribute.id) + } + {...fetchMoreAttributeValues} + /> + )} diff --git a/src/products/components/ProductVariantCreatorPage/__snapshots__/reducer.test.ts.snap b/src/products/components/ProductVariantCreatorPage/__snapshots__/reducer.test.ts.snap index 645de0c75be..1e93d64b024 100644 --- a/src/products/components/ProductVariantCreatorPage/__snapshots__/reducer.test.ts.snap +++ b/src/products/components/ProductVariantCreatorPage/__snapshots__/reducer.test.ts.snap @@ -11,6 +11,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-1", "name": "val-1-1", @@ -24,6 +26,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-7", "name": "val-1-7", @@ -42,6 +46,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-2", "name": "val-2-2", @@ -55,6 +61,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-4", "name": "val-2-4", @@ -73,6 +81,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-1", "name": "val-4-1", @@ -86,6 +96,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-5", "name": "val-4-5", @@ -638,6 +650,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-1", "name": "val-1-1", @@ -651,6 +665,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-7", "name": "val-1-7", @@ -669,6 +685,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-2", "name": "val-2-2", @@ -682,6 +700,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-4", "name": "val-2-4", @@ -700,6 +720,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-1", "name": "val-4-1", @@ -713,6 +735,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-5", "name": "val-4-5", @@ -1265,6 +1289,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-1", "name": "val-1-1", @@ -1278,6 +1304,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-7", "name": "val-1-7", @@ -1296,6 +1324,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-2", "name": "val-2-2", @@ -1309,6 +1339,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-4", "name": "val-2-4", @@ -1327,6 +1359,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-1", "name": "val-4-1", @@ -1340,6 +1374,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-5", "name": "val-4-5", @@ -1392,6 +1428,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-1", "name": "val-1-1", @@ -1405,6 +1443,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-7", "name": "val-1-7", @@ -1423,6 +1463,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-2", "name": "val-2-2", @@ -1436,6 +1478,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-4", "name": "val-2-4", @@ -1454,6 +1498,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-1", "name": "val-4-1", @@ -1467,6 +1513,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-5", "name": "val-4-5", @@ -1970,6 +2018,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-1", "name": "val-1-1", @@ -1983,6 +2033,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-7", "name": "val-1-7", @@ -2001,6 +2053,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-2", "name": "val-2-2", @@ -2014,6 +2068,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-4", "name": "val-2-4", @@ -2032,6 +2088,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-1", "name": "val-4-1", @@ -2045,6 +2103,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-5", "name": "val-4-5", @@ -2503,6 +2563,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-1", "name": "val-1-1", @@ -2516,6 +2578,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-7", "name": "val-1-7", @@ -2534,6 +2598,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-2", "name": "val-2-2", @@ -2547,6 +2613,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-4", "name": "val-2-4", @@ -2565,6 +2633,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-1", "name": "val-4-1", @@ -2578,6 +2648,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-5", "name": "val-4-5", @@ -3081,6 +3153,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-1", "name": "val-1-1", @@ -3094,6 +3168,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-7", "name": "val-1-7", @@ -3112,6 +3188,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-2", "name": "val-2-2", @@ -3125,6 +3203,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-4", "name": "val-2-4", @@ -3143,6 +3223,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-1", "name": "val-4-1", @@ -3156,6 +3238,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-5", "name": "val-4-5", @@ -3576,6 +3660,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-1", "name": "val-1-1", @@ -3589,6 +3675,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-1-7", "name": "val-1-7", @@ -3607,6 +3695,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-2", "name": "val-2-2", @@ -3620,6 +3710,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-2-4", "name": "val-2-4", @@ -3638,6 +3730,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-1", "name": "val-4-1", @@ -3651,6 +3745,8 @@ Object { "value": Object { "__typename": "AttributeValue", "boolean": null, + "date": null, + "dateTime": null, "file": null, "id": "val-4-5", "name": "val-4-5", diff --git a/src/products/components/ProductVariantCreatorPage/createVariants.ts b/src/products/components/ProductVariantCreatorPage/createVariants.ts index 9e0ac7cbc38..a4fbaaa3526 100644 --- a/src/products/components/ProductVariantCreatorPage/createVariants.ts +++ b/src/products/components/ProductVariantCreatorPage/createVariants.ts @@ -11,7 +11,9 @@ import { interface CreateVariantAttributeValueInput { attributeId: string; attributeValueSlug: string; + attributeBooleanValue: boolean | null; } + type CreateVariantInput = CreateVariantAttributeValueInput[]; function findAttribute( @@ -97,7 +99,9 @@ function createVariant( return { attributes: attributes.map(attribute => ({ id: attribute.attributeId, - values: [attribute.attributeValueSlug] + ...(attribute.attributeBooleanValue === null + ? { values: [attribute.attributeValueSlug] } + : { boolean: attribute.attributeBooleanValue }) })), channelListings: price, sku: "", @@ -116,7 +120,8 @@ function addAttributeToVariant( ...variant, { attributeId: attribute.id, - attributeValueSlug: attributeValue.slug + attributeValueSlug: attributeValue.slug, + attributeBooleanValue: attributeValue.value?.boolean ?? null } ]); } @@ -124,11 +129,9 @@ function addVariantAttributeInput( data: CreateVariantInput[], attribute: Attribute ): CreateVariantInput[] { - const variants = data + return data .map(variant => addAttributeToVariant(attribute, variant)) .reduce((acc, variantInput) => [...acc, ...variantInput]); - - return variants; } export function createVariantFlatMatrixDimension( diff --git a/src/products/components/ProductVariantCreatorPage/fixtures.ts b/src/products/components/ProductVariantCreatorPage/fixtures.ts index b4ffc3409b1..205f3662dc2 100644 --- a/src/products/components/ProductVariantCreatorPage/fixtures.ts +++ b/src/products/components/ProductVariantCreatorPage/fixtures.ts @@ -30,7 +30,9 @@ export const attributes = [ file: null, reference: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } })) }, @@ -48,7 +50,9 @@ export const attributes = [ file: null, reference: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } })) }, @@ -66,7 +70,9 @@ export const attributes = [ file: null, reference: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } })) }, @@ -84,7 +90,9 @@ export const attributes = [ file: null, reference: null, richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } })) } diff --git a/src/products/components/ProductVariantCreatorPage/form.ts b/src/products/components/ProductVariantCreatorPage/form.ts index 5e2974a77cd..5afe0eb9cd6 100644 --- a/src/products/components/ProductVariantCreatorPage/form.ts +++ b/src/products/components/ProductVariantCreatorPage/form.ts @@ -29,7 +29,7 @@ export interface Stock { } export interface Attribute { id: string; - values: Array>; + values: Array>>; } export interface ProductVariantCreateFormData { attributes: Attribute[]; diff --git a/src/products/components/ProductVariantCreatorPage/reducer.ts b/src/products/components/ProductVariantCreatorPage/reducer.ts index 30147cb39d4..5e5a3396868 100644 --- a/src/products/components/ProductVariantCreatorPage/reducer.ts +++ b/src/products/components/ProductVariantCreatorPage/reducer.ts @@ -76,7 +76,7 @@ export interface ProductVariantCreateReducerAction { }; selectValue?: { attributeId: string; - value: AttributeValue; + value: AttributeValue>; }; type: ProductVariantCreateReducerActionType; } @@ -84,11 +84,12 @@ export interface ProductVariantCreateReducerAction { function selectValue( prevState: ProductVariantCreateFormData, attributeId: string, - value: AttributeValue + value: AttributeValue> ): ProductVariantCreateFormData { const attribute = prevState.attributes.find( attribute => attribute.id === attributeId ); + const values = toggle(value, attribute.values, (a, b) => a.slug === b.slug); const updatedAttributes = add( { diff --git a/src/products/components/ProductVariantCreatorPage/utils.ts b/src/products/components/ProductVariantCreatorPage/utils.ts index 51998d192c7..9e3fe44a2c8 100644 --- a/src/products/components/ProductVariantCreatorPage/utils.ts +++ b/src/products/components/ProductVariantCreatorPage/utils.ts @@ -1,9 +1,12 @@ +import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFragment"; +import { getById } from "@saleor/orders/components/OrderReturnPage/utils"; import { ProductDetails_product_productType_variantAttributes, ProductDetails_product_productType_variantAttributes_choices_edges_node } from "@saleor/products/types/ProductDetails"; +import { SearchAttributeValues_attribute_choices_edges_node } from "@saleor/searches/types/SearchAttributeValues"; -import { ProductVariantCreateFormData } from "./form"; +import { AttributeValue, ProductVariantCreateFormData } from "./form"; export function getPriceAttributeValues( data: ProductVariantCreateFormData, @@ -44,3 +47,33 @@ export function getStockAttributeValues( .map(value => value.node) : []; } + +export const getBySlug = (slugToCompare: string) => (obj: { slug: string }) => + obj.slug === slugToCompare; + +export const getBooleanAttributeValue = ( + attributeName: string, + attributeValue: boolean +): AttributeValue> => ({ + slug: attributeValue.toString(), + value: { + boolean: attributeValue, + name: `${attributeName}: ${attributeValue ? "Yes" : "No"}` + } +}); + +export const getBasicAttributeValue = ( + attributeId: string, + attributeValue: string, + attributeValues: SearchAttributeValues_attribute_choices_edges_node[], + data: ProductVariantCreateFormData +): AttributeValue> => { + const dataAttribute = data.attributes.find(getById(attributeId)); + + return { + slug: attributeValue, + value: + dataAttribute?.values.find(getBySlug(attributeValue))?.value || + attributeValues.find(getBySlug(attributeValue)) + }; +}; diff --git a/src/products/fixtures.ts b/src/products/fixtures.ts index ecf82a4a512..9c3d1ebbbcb 100644 --- a/src/products/fixtures.ts +++ b/src/products/fixtures.ts @@ -51,7 +51,9 @@ export const product: ( reference: null, slug: "portals", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -65,7 +67,9 @@ export const product: ( reference: null, slug: "Baht", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -80,7 +84,9 @@ export const product: ( reference: null, slug: "portals", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, @@ -116,7 +122,9 @@ export const product: ( reference: null, slug: "payment", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -130,7 +138,9 @@ export const product: ( reference: null, slug: "Auto-Loan-Account", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -144,7 +154,9 @@ export const product: ( reference: null, slug: "Garden", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -158,7 +170,9 @@ export const product: ( reference: null, slug: "override", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -173,7 +187,9 @@ export const product: ( reference: null, slug: "Auto-Loan-Account", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -369,7 +385,9 @@ export const product: ( reference: null, slug: "file-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -407,7 +425,9 @@ export const product: ( reference: null, slug: "black", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -421,7 +441,9 @@ export const product: ( reference: null, slug: "white", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -438,6 +460,8 @@ export const product: ( __typename: "Attribute", id: "isdugfhud", name: "Attachment", + inputType: AttributeInputTypeEnum.DROPDOWN, + unit: null, choices: { __typename: "AttributeValueCountableConnection", pageInfo: { @@ -463,7 +487,9 @@ export const product: ( reference: null, slug: "file-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -473,6 +499,8 @@ export const product: ( __typename: "Attribute", id: "pta18161", name: "Color", + inputType: AttributeInputTypeEnum.DROPDOWN, + unit: null, choices: { __typename: "AttributeValueCountableConnection", pageInfo: { @@ -494,7 +522,9 @@ export const product: ( reference: null, slug: "black", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -508,7 +538,9 @@ export const product: ( reference: null, slug: "white", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -968,7 +1000,9 @@ export const products = ( reference: null, slug: "pineapple", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1078,7 +1112,9 @@ export const products = ( reference: null, slug: "coconut", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1188,7 +1224,9 @@ export const products = ( reference: null, slug: "apple", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1299,7 +1337,9 @@ export const products = ( reference: null, slug: "orange", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1409,7 +1449,9 @@ export const products = ( reference: null, slug: "banana", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1519,7 +1561,9 @@ export const products = ( reference: null, slug: "bean", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1629,7 +1673,9 @@ export const products = ( reference: null, slug: "carrot", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1739,7 +1785,9 @@ export const products = ( reference: null, slug: "sprouty", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1849,7 +1897,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -1959,7 +2009,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -2069,7 +2121,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -2179,7 +2233,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -2289,7 +2345,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -2399,7 +2457,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -2509,7 +2569,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -2619,7 +2681,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -2729,7 +2793,9 @@ export const products = ( reference: null, slug: "cotton", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -2941,7 +3007,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "file-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -2960,7 +3028,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "file-first-value", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } @@ -3222,7 +3292,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "portals", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -3236,7 +3308,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "Baht", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -3251,7 +3325,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "portals", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] }, @@ -3287,7 +3363,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "payment", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -3301,7 +3379,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "Auto-Loan-Account", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -3315,7 +3395,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "Garden", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } }, { @@ -3329,7 +3411,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "override", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } } ] @@ -3344,7 +3428,9 @@ export const variant = (placeholderImage: string): ProductVariant => ({ reference: null, slug: "Auto-Loan-Account", richText: null, - boolean: null + boolean: null, + date: null, + dateTime: null } ] } diff --git a/src/products/types/CreateMultipleVariantsData.ts b/src/products/types/CreateMultipleVariantsData.ts index 21f3306a177..c1552804588 100644 --- a/src/products/types/CreateMultipleVariantsData.ts +++ b/src/products/types/CreateMultipleVariantsData.ts @@ -32,6 +32,8 @@ export interface CreateMultipleVariantsData_product_attributes_attribute_choices reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface CreateMultipleVariantsData_product_attributes_attribute_choices_edges { @@ -73,6 +75,8 @@ export interface CreateMultipleVariantsData_product_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface CreateMultipleVariantsData_product_attributes { @@ -104,6 +108,8 @@ export interface CreateMultipleVariantsData_product_productType_variantAttribute reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface CreateMultipleVariantsData_product_productType_variantAttributes_choices_edges { @@ -122,6 +128,8 @@ export interface CreateMultipleVariantsData_product_productType_variantAttribute __typename: "Attribute"; id: string; name: string | null; + inputType: AttributeInputTypeEnum | null; + unit: MeasurementUnitsEnum | null; choices: CreateMultipleVariantsData_product_productType_variantAttributes_choices | null; } diff --git a/src/products/types/ProductDetails.ts b/src/products/types/ProductDetails.ts index de08c963afb..110ac8e1221 100644 --- a/src/products/types/ProductDetails.ts +++ b/src/products/types/ProductDetails.ts @@ -32,6 +32,8 @@ export interface ProductDetails_product_attributes_attribute_choices_edges_node reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductDetails_product_attributes_attribute_choices_edges { @@ -73,6 +75,8 @@ export interface ProductDetails_product_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductDetails_product_attributes { @@ -104,6 +108,8 @@ export interface ProductDetails_product_productType_variantAttributes_choices_ed reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductDetails_product_productType_variantAttributes_choices_edges { @@ -122,6 +128,8 @@ export interface ProductDetails_product_productType_variantAttributes { __typename: "Attribute"; id: string; name: string | null; + inputType: AttributeInputTypeEnum | null; + unit: MeasurementUnitsEnum | null; choices: ProductDetails_product_productType_variantAttributes_choices | null; } diff --git a/src/products/types/ProductList.ts b/src/products/types/ProductList.ts index 031a9b896eb..5c60a59f6be 100644 --- a/src/products/types/ProductList.ts +++ b/src/products/types/ProductList.ts @@ -92,6 +92,8 @@ export interface ProductList_products_edges_node_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductList_products_edges_node_attributes { diff --git a/src/products/types/ProductType.ts b/src/products/types/ProductType.ts index ac006f2f978..a795d0a8778 100644 --- a/src/products/types/ProductType.ts +++ b/src/products/types/ProductType.ts @@ -32,6 +32,8 @@ export interface ProductType_productType_productAttributes_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductType_productType_productAttributes_choices_edges { diff --git a/src/products/types/ProductUpdate.ts b/src/products/types/ProductUpdate.ts index 130fef1949a..c2dd45cffeb 100644 --- a/src/products/types/ProductUpdate.ts +++ b/src/products/types/ProductUpdate.ts @@ -39,6 +39,8 @@ export interface ProductUpdate_productUpdate_product_attributes_attribute_choice reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductUpdate_productUpdate_product_attributes_attribute_choices_edges { @@ -80,6 +82,8 @@ export interface ProductUpdate_productUpdate_product_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductUpdate_productUpdate_product_attributes { @@ -111,6 +115,8 @@ export interface ProductUpdate_productUpdate_product_productType_variantAttribut reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductUpdate_productUpdate_product_productType_variantAttributes_choices_edges { @@ -129,6 +135,8 @@ export interface ProductUpdate_productUpdate_product_productType_variantAttribut __typename: "Attribute"; id: string; name: string | null; + inputType: AttributeInputTypeEnum | null; + unit: MeasurementUnitsEnum | null; choices: ProductUpdate_productUpdate_product_productType_variantAttributes_choices | null; } diff --git a/src/products/types/ProductVariantCreateData.ts b/src/products/types/ProductVariantCreateData.ts index a86d7b78782..3565674e972 100644 --- a/src/products/types/ProductVariantCreateData.ts +++ b/src/products/types/ProductVariantCreateData.ts @@ -51,6 +51,8 @@ export interface ProductVariantCreateData_product_productType_selectionVariantAt reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantCreateData_product_productType_selectionVariantAttributes_choices_edges { @@ -100,6 +102,8 @@ export interface ProductVariantCreateData_product_productType_nonSelectionVarian reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantCreateData_product_productType_nonSelectionVariantAttributes_choices_edges { diff --git a/src/products/types/ProductVariantDetails.ts b/src/products/types/ProductVariantDetails.ts index d41e07c15ff..4b2950da7c3 100644 --- a/src/products/types/ProductVariantDetails.ts +++ b/src/products/types/ProductVariantDetails.ts @@ -44,6 +44,8 @@ export interface ProductVariantDetails_productVariant_selectionAttributes_attrib reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantDetails_productVariant_selectionAttributes_attribute_choices_edges { @@ -85,6 +87,8 @@ export interface ProductVariantDetails_productVariant_selectionAttributes_values reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantDetails_productVariant_selectionAttributes { @@ -116,6 +120,8 @@ export interface ProductVariantDetails_productVariant_nonSelectionAttributes_att reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantDetails_productVariant_nonSelectionAttributes_attribute_choices_edges { @@ -157,6 +163,8 @@ export interface ProductVariantDetails_productVariant_nonSelectionAttributes_val reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface ProductVariantDetails_productVariant_nonSelectionAttributes { diff --git a/src/products/types/SimpleProductUpdate.ts b/src/products/types/SimpleProductUpdate.ts index ebd67067da4..c68cb9f578e 100644 --- a/src/products/types/SimpleProductUpdate.ts +++ b/src/products/types/SimpleProductUpdate.ts @@ -39,6 +39,8 @@ export interface SimpleProductUpdate_productUpdate_product_attributes_attribute_ reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productUpdate_product_attributes_attribute_choices_edges { @@ -80,6 +82,8 @@ export interface SimpleProductUpdate_productUpdate_product_attributes_values { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productUpdate_product_attributes { @@ -111,6 +115,8 @@ export interface SimpleProductUpdate_productUpdate_product_productType_variantAt reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productUpdate_product_productType_variantAttributes_choices_edges { @@ -129,6 +135,8 @@ export interface SimpleProductUpdate_productUpdate_product_productType_variantAt __typename: "Attribute"; id: string; name: string | null; + inputType: AttributeInputTypeEnum | null; + unit: MeasurementUnitsEnum | null; choices: SimpleProductUpdate_productUpdate_product_productType_variantAttributes_choices | null; } @@ -379,6 +387,8 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_selecti reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantUpdate_productVariant_selectionAttributes_attribute_choices_edges { @@ -420,6 +430,8 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_selecti reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantUpdate_productVariant_selectionAttributes { @@ -451,6 +463,8 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_nonSele reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantUpdate_productVariant_nonSelectionAttributes_attribute_choices_edges { @@ -492,6 +506,8 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_nonSele reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantUpdate_productVariant_nonSelectionAttributes { @@ -714,6 +730,8 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_s reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_selectionAttributes_attribute_choices_edges { @@ -755,6 +773,8 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_s reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_selectionAttributes { @@ -786,6 +806,8 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_n reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_nonSelectionAttributes_attribute_choices_edges { @@ -827,6 +849,8 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_n reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_nonSelectionAttributes { @@ -1048,6 +1072,8 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_s reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_selectionAttributes_attribute_choices_edges { @@ -1089,6 +1115,8 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_s reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_selectionAttributes { @@ -1120,6 +1148,8 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_n reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_nonSelectionAttributes_attribute_choices_edges { @@ -1161,6 +1191,8 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_n reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_nonSelectionAttributes { @@ -1383,6 +1415,8 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_s reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_selectionAttributes_attribute_choices_edges { @@ -1424,6 +1458,8 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_s reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_selectionAttributes { @@ -1455,6 +1491,8 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_n reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_nonSelectionAttributes_attribute_choices_edges { @@ -1496,6 +1534,8 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_n reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_nonSelectionAttributes { diff --git a/src/products/types/VariantCreate.ts b/src/products/types/VariantCreate.ts index 75ed9ee8223..90cb1d9dd38 100644 --- a/src/products/types/VariantCreate.ts +++ b/src/products/types/VariantCreate.ts @@ -51,6 +51,8 @@ export interface VariantCreate_productVariantCreate_productVariant_selectionAttr reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantCreate_productVariantCreate_productVariant_selectionAttributes_attribute_choices_edges { @@ -92,6 +94,8 @@ export interface VariantCreate_productVariantCreate_productVariant_selectionAttr reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantCreate_productVariantCreate_productVariant_selectionAttributes { @@ -123,6 +127,8 @@ export interface VariantCreate_productVariantCreate_productVariant_nonSelectionA reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantCreate_productVariantCreate_productVariant_nonSelectionAttributes_attribute_choices_edges { @@ -164,6 +170,8 @@ export interface VariantCreate_productVariantCreate_productVariant_nonSelectionA reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantCreate_productVariantCreate_productVariant_nonSelectionAttributes { diff --git a/src/products/types/VariantUpdate.ts b/src/products/types/VariantUpdate.ts index 4af9bfa8248..995423060f3 100644 --- a/src/products/types/VariantUpdate.ts +++ b/src/products/types/VariantUpdate.ts @@ -51,6 +51,8 @@ export interface VariantUpdate_productVariantUpdate_productVariant_selectionAttr reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantUpdate_productVariantUpdate_productVariant_selectionAttributes_attribute_choices_edges { @@ -92,6 +94,8 @@ export interface VariantUpdate_productVariantUpdate_productVariant_selectionAttr reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantUpdate_productVariantUpdate_productVariant_selectionAttributes { @@ -123,6 +127,8 @@ export interface VariantUpdate_productVariantUpdate_productVariant_nonSelectionA reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantUpdate_productVariantUpdate_productVariant_nonSelectionAttributes_attribute_choices_edges { @@ -164,6 +170,8 @@ export interface VariantUpdate_productVariantUpdate_productVariant_nonSelectionA reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantUpdate_productVariantUpdate_productVariant_nonSelectionAttributes { @@ -386,6 +394,8 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_selecti reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantUpdate_productVariantStocksUpdate_productVariant_selectionAttributes_attribute_choices_edges { @@ -427,6 +437,8 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_selecti reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantUpdate_productVariantStocksUpdate_productVariant_selectionAttributes { @@ -458,6 +470,8 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_nonSele reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantUpdate_productVariantStocksUpdate_productVariant_nonSelectionAttributes_attribute_choices_edges { @@ -499,6 +513,8 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_nonSele reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface VariantUpdate_productVariantStocksUpdate_productVariant_nonSelectionAttributes { diff --git a/src/products/utils/data.ts b/src/products/utils/data.ts index 87aead510f1..c9cf67c0118 100644 --- a/src/products/utils/data.ts +++ b/src/products/utils/data.ts @@ -47,22 +47,20 @@ export interface ProductType { export function getAttributeInputFromProduct( product: ProductDetails_product ): AttributeInput[] { - return maybe( - (): AttributeInput[] => - product.attributes.map(attribute => ({ - data: { - entityType: attribute.attribute.entityType, - inputType: attribute.attribute.inputType, - isRequired: attribute.attribute.valueRequired, - selectedValues: attribute.values, - values: mergeChoicesWithValues(attribute), - unit: attribute.attribute.unit - }, - id: attribute.attribute.id, - label: attribute.attribute.name, - value: getSelectedAttributeValues(attribute) - })), - [] + return ( + product?.attributes?.map(attribute => ({ + data: { + entityType: attribute.attribute.entityType, + inputType: attribute.attribute.inputType, + isRequired: attribute.attribute.valueRequired, + selectedValues: attribute.values, + values: mergeChoicesWithValues(attribute), + unit: attribute.attribute.unit + }, + id: attribute.attribute.id, + label: attribute.attribute.name, + value: getSelectedAttributeValues(attribute) + })) ?? [] ); } diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index f64ef34d9ff..c0ee3b4230e 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -31,6 +31,7 @@ import { mapSlugNodeToChoice } from "@saleor/utils/maps"; import isArray from "lodash/isArray"; +import moment from "moment-timezone"; import { IFilterElement } from "../../../components/Filter"; import { @@ -45,7 +46,8 @@ import { getMinMaxQueryParam, getMultipleValueQueryParam, getSingleEnumValueQueryParam, - getSingleValueQueryParam + getSingleValueQueryParam, + GteLte } from "../../../utils/filters"; import { ProductListUrlFilters, @@ -54,7 +56,6 @@ import { ProductListUrlFiltersWithMultipleValues, ProductListUrlQueryParams } from "../../urls"; - export const PRODUCT_FILTERS_KEY = "productFilters"; export function getFilterOpts( @@ -204,25 +205,93 @@ export function getFilterOpts( }; } +const parseFilterValue = ( + params: ProductListUrlFilters, + key: string +): { + type: "boolean" | "date" | "dateTime" | "string"; + isMulti: boolean; + value: string[]; +} => { + const value = params.attributes[key]; + const isMulti = isArray(params.attributes[key]); + + const isBooleanValue = + !isMulti && ["true", "false"].includes((value as unknown) as string); + const isDateValue = (isMulti ? value : [value]).some(val => + moment(val, moment.HTML5_FMT.DATE, true).isValid() + ); + const isDateTimeValue = (isMulti ? value : [value]).some(val => + moment(val, moment.ISO_8601, true).isValid() + ); + + const data = { isMulti, value: (isMulti ? value : [value]) as string[] }; + + if (isBooleanValue) { + return { ...data, type: "boolean" }; + } else if (isDateValue) { + return { ...data, type: "date" }; + } else if (isDateTimeValue) { + return { ...data, type: "dateTime" }; + } + return { ...data, type: "string" }; +}; + +interface BaseFilterParam { + slug: string; +} +interface BooleanFilterParam extends BaseFilterParam { + boolean: boolean; +} +interface DateFilterParam extends BaseFilterParam { + date: GteLte; +} +interface DateTimeFilterParam extends BaseFilterParam { + dateTime: GteLte; +} +interface DefaultFilterParam extends BaseFilterParam { + values: string[]; +} + function getFilteredAttributeValue( params: ProductListUrlFilters -): Array<({ boolean: boolean } | { values: string[] }) & { slug: string }> { +): Array< + | BooleanFilterParam + | BaseFilterParam + | DateTimeFilterParam + | DateFilterParam + | DefaultFilterParam +> { return !!params.attributes ? Object.keys(params.attributes).map(key => { - const value = params.attributes[key]; - const isMulti = isArray(params.attributes[key]); - const isBooleanValue = - !isMulti && ["true", "false"].includes((value as unknown) as string); + const { isMulti, type, value } = parseFilterValue(params, key); + const name = { slug: key }; - return { - slug: key, - ...(isBooleanValue - ? { boolean: JSON.parse((value as unknown) as string) } - : { - // It is possible for qs to parse values not as string[] but string - values: isMulti ? value : (([value] as unknown) as string[]) + switch (type) { + case "boolean": + return { ...name, boolean: JSON.parse(value[0]) }; + + case "date": + return { + ...name, + date: getGteLteVariables({ + gte: value[0] || null, + lte: isMulti ? value[1] || null : value[0] + }) + }; + + case "dateTime": + return { + ...name, + dateTime: getGteLteVariables({ + gte: value[0] || null, + lte: isMulti ? value[1] || null : value[0] }) - }; + }; + + default: + return { ...name, values: value }; + } }) : null; } diff --git a/src/searches/types/SearchAttributeValues.ts b/src/searches/types/SearchAttributeValues.ts index 77abb76442f..ff74b875d3d 100644 --- a/src/searches/types/SearchAttributeValues.ts +++ b/src/searches/types/SearchAttributeValues.ts @@ -22,6 +22,8 @@ export interface SearchAttributeValues_attribute_choices_edges_node { reference: string | null; richText: any | null; boolean: boolean | null; + date: any | null; + dateTime: any | null; } export interface SearchAttributeValues_attribute_choices_edges { diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index b12276a3b8b..ca6f9be3246 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -61,7 +61,7 @@ exports[`Storyshots Attributes / Attributes default 1`] = `
- 7 Attributes + 9 Attributes
-
+
+
+
+
+
+ Date Attribute +
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ Date Time Attribute +
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
@@ -520,7 +670,7 @@ exports[`Storyshots Attributes / Attributes disabled 1`] = `
- 7 Attributes + 9 Attributes
-
+
+
+
+
+
+ Date Attribute +
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ Date Time Attribute +
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
@@ -986,7 +1289,7 @@ exports[`Storyshots Attributes / Attributes selected 1`] = `
- 7 Attributes + 9 Attributes
-
+
+
+
+
+
+ Date Attribute +
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ Date Time Attribute +
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
@@ -2778,7 +3232,9 @@ exports[`Storyshots Generics / Account Permission Groups Widget default 1`] = ` type="text" value="" /> -
+
-
+