From b07bb08ade3f4e9c6cf69b72fb21006316cf95fb Mon Sep 17 00:00:00 2001 From: Dawid Tarasiuk Date: Mon, 18 Jan 2021 16:37:38 +0100 Subject: [PATCH] 1863 - Support reference attribute reordering (#946) * Support reference attribute reordering * Update attribute handlers tests --- .../utils/handlers.test.ts | 3 +- src/attributes/utils/handlers.ts | 106 ++++- src/components/Attributes/Attributes.tsx | 20 +- .../SortableChipsField.stories.tsx | 9 +- .../SortableChipsField/SortableChipsField.tsx | 20 +- .../PageDetailsPage/PageDetailsPage.tsx | 1 + src/pages/components/PageDetailsPage/form.tsx | 21 +- src/pages/utils/handlers.ts | 34 +- .../ProductCreatePage/ProductCreatePage.tsx | 1 + .../components/ProductCreatePage/form.tsx | 19 +- .../ProductUpdatePage/ProductUpdatePage.tsx | 1 + .../components/ProductUpdatePage/form.tsx | 19 +- .../ProductVariantCreatePage.tsx | 4 + .../ProductVariantCreatePage/form.tsx | 25 +- .../ProductVariantPage/ProductVariantPage.tsx | 2 + .../components/ProductVariantPage/form.tsx | 25 +- src/products/utils/handlers.test.ts | 142 ------- src/products/utils/handlers.ts | 77 +--- .../__snapshots__/Stories.test.ts.snap | 383 ++++++++++++++++++ 19 files changed, 615 insertions(+), 297 deletions(-) rename src/{pages => attributes}/utils/handlers.test.ts (97%) delete mode 100644 src/products/utils/handlers.test.ts diff --git a/src/pages/utils/handlers.test.ts b/src/attributes/utils/handlers.test.ts similarity index 97% rename from src/pages/utils/handlers.test.ts rename to src/attributes/utils/handlers.test.ts index d750a9f83..b85684fa8 100644 --- a/src/pages/utils/handlers.test.ts +++ b/src/attributes/utils/handlers.test.ts @@ -1,9 +1,8 @@ +import { createAttributeMultiChangeHandler } from "@saleor/attributes/utils/handlers"; import { AttributeInputData } from "@saleor/components/Attributes"; import { FormsetData } from "@saleor/hooks/useFormset"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; -import { createAttributeMultiChangeHandler } from "./handlers"; - const attributes: FormsetData = [ { data: { diff --git a/src/attributes/utils/handlers.ts b/src/attributes/utils/handlers.ts index e1d8ca82d..afddebd11 100644 --- a/src/attributes/utils/handlers.ts +++ b/src/attributes/utils/handlers.ts @@ -1,14 +1,23 @@ -import { AttributeInput } from "@saleor/components/Attributes"; +import { + AttributeInput, + AttributeInputData +} from "@saleor/components/Attributes"; import { FileUpload, FileUploadVariables } from "@saleor/files/types/FileUpload"; -import { FormsetData } from "@saleor/hooks/useFormset"; +import { + FormsetAtomicData, + FormsetChange, + FormsetData +} from "@saleor/hooks/useFormset"; import { PageDetails_page_attributes } from "@saleor/pages/types/PageDetails"; +import { ReorderEvent } from "@saleor/types"; import { AttributeInputTypeEnum, AttributeValueInput } from "@saleor/types/globalTypes"; +import { move, toggle } from "@saleor/utils/lists"; import { MutationFetchResult } from "react-apollo"; import { @@ -17,6 +26,99 @@ import { } from "../types/AttributeValueDelete"; import { getFileValuesToUploadFromAttributes, isFileValueUnused } from "./data"; +export function createAttributeChangeHandler( + changeAttributeData: FormsetChange, + triggerChange: () => void +): FormsetChange { + return (attributeId: string, value: string) => { + triggerChange(); + changeAttributeData(attributeId, value === "" ? [] : [value]); + }; +} + +export function createAttributeMultiChangeHandler( + changeAttributeData: FormsetChange, + attributes: FormsetData, + triggerChange: () => void +): FormsetChange { + return (attributeId: string, value: string) => { + const attribute = attributes.find( + attribute => attribute.id === attributeId + ); + + const newAttributeValues = toggle( + value, + attribute.value, + (a, b) => a === b + ); + + triggerChange(); + changeAttributeData(attributeId, newAttributeValues); + }; +} + +export function createAttributeReferenceChangeHandler( + changeAttributeData: FormsetChange, + triggerChange: () => void +): FormsetChange { + return (attributeId: string, values: string[]) => { + changeAttributeData(attributeId, values); + triggerChange(); + }; +} + +export function createAttributeFileChangeHandler( + changeAttributeData: FormsetChange, + attributesWithNewFileValue: FormsetData>, + addAttributeNewFileValue: (data: FormsetAtomicData) => void, + changeAttributeNewFileValue: FormsetChange, + triggerChange: () => void +): FormsetChange { + return (attributeId: string, value: File) => { + triggerChange(); + + const newFileValueAssigned = attributesWithNewFileValue.find( + attribute => attribute.id === attributeId + ); + + if (newFileValueAssigned) { + changeAttributeNewFileValue(attributeId, value); + } else { + addAttributeNewFileValue({ + data: null, + id: attributeId, + label: null, + value + }); + } + + changeAttributeData(attributeId, value ? [value.name] : []); + }; +} + +export function createAttributeValueReorderHandler( + changeAttributeData: FormsetChange, + attributes: FormsetData, + triggerChange: () => void +): FormsetChange { + return (attributeId: string, reorder: ReorderEvent) => { + triggerChange(); + + const attribute = attributes.find( + attribute => attribute.id === attributeId + ); + + const reorderedValues = move( + attribute.value[reorder.oldIndex], + attribute.value, + (a, b) => a === b, + reorder.newIndex + ); + + changeAttributeData(attributeId, reorderedValues); + }; +} + interface AttributesArgs { attributes: AttributeInput[]; updatedFileAttributes: AttributeValueInput[]; diff --git a/src/components/Attributes/Attributes.tsx b/src/components/Attributes/Attributes.tsx index c06644f45..95de1fb5b 100644 --- a/src/components/Attributes/Attributes.tsx +++ b/src/components/Attributes/Attributes.tsx @@ -17,7 +17,7 @@ import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErr import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages"; -import { ReorderAction } from "@saleor/types"; +import { ReorderEvent } from "@saleor/types"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { getProductErrorMessage } from "@saleor/utils/errors"; import getPageErrorMessage from "@saleor/utils/errors/page"; @@ -56,12 +56,12 @@ export interface AttributesProps { ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment >; title?: React.ReactNode; - onChange: FormsetChange; - onMultiChange: FormsetChange; - onFileChange: FormsetChange; - onReferencesRemove?: FormsetChange; // TODO: temporairy optional, should be changed to required, after all pages implement it - onReferencesAddClick?: (attribute: AttributeInput) => void; // TODO: temporairy optional, should be changed to required, after all pages implement it - onReferencesReorder?: ReorderAction; // TODO: temporairy optional, should be changed to required, after all pages implement it + onChange: FormsetChange; + onMultiChange: FormsetChange; + onFileChange: FormsetChange; + onReferencesRemove: FormsetChange; + onReferencesAddClick: (attribute: AttributeInput) => void; + onReferencesReorder: FormsetChange; } const useStyles = makeStyles( @@ -328,8 +328,12 @@ const Attributes: React.FC = ({ attribute.value?.filter(id => id !== value) ) } - onValueReorder={onReferencesReorder} + onValueReorder={event => + onReferencesReorder(attribute.id, event) + } loading={loading} + error={!!error} + helperText={getErrorMessage(error, intl)} /> ) : attribute.data.inputType === diff --git a/src/components/SortableChipsField/SortableChipsField.stories.tsx b/src/components/SortableChipsField/SortableChipsField.stories.tsx index 5f3f383da..b8e41e69b 100644 --- a/src/components/SortableChipsField/SortableChipsField.stories.tsx +++ b/src/components/SortableChipsField/SortableChipsField.stories.tsx @@ -24,4 +24,11 @@ storiesOf("Generics / Sortable chips field", module) .addDecorator(CardDecorator) .addDecorator(Decorator) .add("default", () => ) - .add("loading", () => ); + .add("loading", () => ) + .add("with error", () => ( + + )); diff --git a/src/components/SortableChipsField/SortableChipsField.tsx b/src/components/SortableChipsField/SortableChipsField.tsx index a2282d441..e580b082d 100644 --- a/src/components/SortableChipsField/SortableChipsField.tsx +++ b/src/components/SortableChipsField/SortableChipsField.tsx @@ -1,4 +1,5 @@ import { makeStyles } from "@material-ui/core/styles"; +import Typography from "@material-ui/core/Typography"; import { ReorderAction } from "@saleor/types"; import React from "react"; import { SortableContainerProps } from "react-sortable-hoc"; @@ -13,6 +14,9 @@ const useStyles = makeStyles( background: "#fff", color: theme.palette.primary.dark, marginBottom: theme.spacing(1) + }, + errorText: { + color: theme.palette.error.light } }), { @@ -28,12 +32,21 @@ export interface SortableChipsFieldValueType { export interface SortableChipsFieldProps extends SortableContainerProps { loading?: boolean; values: SortableChipsFieldValueType[]; + error?: boolean; + helperText?: string; onValueDelete: (id: string) => void; onValueReorder: ReorderAction; } const SortableChipsField: React.FC = props => { - const { loading, values, onValueDelete, onValueReorder } = props; + const { + loading, + values, + error, + helperText, + onValueDelete, + onValueReorder + } = props; const classes = useStyles(props); return ( @@ -57,6 +70,11 @@ const SortableChipsField: React.FC = props => { /> )) )} + {error && ( + + {helperText} + + )} ); diff --git a/src/pages/components/PageDetailsPage/PageDetailsPage.tsx b/src/pages/components/PageDetailsPage/PageDetailsPage.tsx index 6b6dadc5a..edfeb5841 100644 --- a/src/pages/components/PageDetailsPage/PageDetailsPage.tsx +++ b/src/pages/components/PageDetailsPage/PageDetailsPage.tsx @@ -148,6 +148,7 @@ const PageDetailsPage: React.FC = ({ onFileChange={handlers.selectAttributeFile} onReferencesRemove={handlers.selectAttributeReference} onReferencesAddClick={onAssignReferencesClick} + onReferencesReorder={handlers.reorderAttributeValue} /> )} diff --git a/src/pages/components/PageDetailsPage/form.tsx b/src/pages/components/PageDetailsPage/form.tsx index 91024107d..cec3a130e 100644 --- a/src/pages/components/PageDetailsPage/form.tsx +++ b/src/pages/components/PageDetailsPage/form.tsx @@ -1,5 +1,12 @@ import { OutputData } from "@editorjs/editorjs"; import { getAttributesDisplayData } from "@saleor/attributes/utils/data"; +import { + createAttributeChangeHandler, + createAttributeFileChangeHandler, + createAttributeMultiChangeHandler, + createAttributeReferenceChangeHandler, + createAttributeValueReorderHandler +} from "@saleor/attributes/utils/handlers"; import { AttributeInput } from "@saleor/components/Attributes"; import { MetadataFormData } from "@saleor/components/Metadata"; import { RichTextEditorChange } from "@saleor/components/RichTextEditor"; @@ -16,13 +23,8 @@ import { } from "@saleor/pages/types/PageDetails"; import { getAttributeInputFromPage } from "@saleor/pages/utils/data"; import { createPageTypeSelectHandler } from "@saleor/pages/utils/handlers"; -import { - createAttributeChangeHandler, - createAttributeFileChangeHandler, - createAttributeMultiChangeHandler, - createAttributeReferenceChangeHandler -} from "@saleor/products/utils/handlers"; import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages"; +import { ReorderEvent } from "@saleor/types"; import getPublicationData from "@saleor/utils/data/getPublicationData"; import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit"; import { mapMetadataItemToInput } from "@saleor/utils/maps"; @@ -59,6 +61,7 @@ export interface PageUpdateHandlers { selectAttributeMulti: FormsetChange; selectAttributeReference: FormsetChange; selectAttributeFile: FormsetChange; + reorderAttributeValue: FormsetChange; } export interface UsePageUpdateFormResult { change: FormChange; @@ -150,6 +153,11 @@ function usePageForm( attributesWithNewFileValue.change, triggerChange ); + const handleAttributeValueReorder = createAttributeValueReorderHandler( + attributes.change, + attributes.data, + triggerChange + ); // Need to make it function to always have content.current up to date const getData = (): PageData => ({ @@ -190,6 +198,7 @@ function usePageForm( handlers: { changeContent, changeMetadata, + reorderAttributeValue: handleAttributeValueReorder, selectAttribute: handleAttributeChange, selectAttributeFile: handleAttributeFileChange, selectAttributeMulti: handleAttributeMultiChange, diff --git a/src/pages/utils/handlers.ts b/src/pages/utils/handlers.ts index a97db47ed..a4685a40a 100644 --- a/src/pages/utils/handlers.ts +++ b/src/pages/utils/handlers.ts @@ -1,7 +1,6 @@ import { AttributeInputData } from "@saleor/components/Attributes"; import { FormChange } from "@saleor/hooks/useForm"; -import { FormsetChange, FormsetData } from "@saleor/hooks/useFormset"; -import { toggle } from "@saleor/utils/lists"; +import { FormsetData } from "@saleor/hooks/useFormset"; import { PageDetails_page_pageType } from "../types/PageDetails"; import { getAttributeInputFromPageType } from "./data"; @@ -23,34 +22,3 @@ export function createPageTypeSelectHandler( setAttributes(getAttributeInputFromPageType(selectedPageType)); }; } - -export function createAttributeChangeHandler( - changeAttributeData: FormsetChange, - triggerChange: () => void -): FormsetChange { - return (attributeId: string, value: string) => { - triggerChange(); - changeAttributeData(attributeId, value === "" ? [] : [value]); - }; -} - -export function createAttributeMultiChangeHandler( - changeAttributeData: FormsetChange, - attributes: FormsetData, - triggerChange: () => void -): FormsetChange { - return (attributeId: string, value: string) => { - const attribute = attributes.find( - attribute => attribute.id === attributeId - ); - - const newAttributeValues = toggle( - value, - attribute.value, - (a, b) => a === b - ); - - triggerChange(); - changeAttributeData(attributeId, newAttributeValues); - }; -} diff --git a/src/products/components/ProductCreatePage/ProductCreatePage.tsx b/src/products/components/ProductCreatePage/ProductCreatePage.tsx index 595f0a714..5a54d0892 100644 --- a/src/products/components/ProductCreatePage/ProductCreatePage.tsx +++ b/src/products/components/ProductCreatePage/ProductCreatePage.tsx @@ -206,6 +206,7 @@ export const ProductCreatePage: React.FC = ({ onFileChange={handlers.selectAttributeFile} onReferencesRemove={handlers.selectAttributeReference} onReferencesAddClick={onAssignReferencesClick} + onReferencesReorder={handlers.reorderAttributeValue} /> )} diff --git a/src/products/components/ProductCreatePage/form.tsx b/src/products/components/ProductCreatePage/form.tsx index 682a7775f..4e534659d 100644 --- a/src/products/components/ProductCreatePage/form.tsx +++ b/src/products/components/ProductCreatePage/form.tsx @@ -1,5 +1,12 @@ import { OutputData } from "@editorjs/editorjs"; import { getAttributesDisplayData } from "@saleor/attributes/utils/data"; +import { + createAttributeChangeHandler, + createAttributeFileChangeHandler, + createAttributeMultiChangeHandler, + createAttributeReferenceChangeHandler, + createAttributeValueReorderHandler +} from "@saleor/attributes/utils/handlers"; import { ChannelData, ChannelPriceArgs } from "@saleor/channels/utils"; import { AttributeInput, @@ -20,10 +27,6 @@ import { ProductType } from "@saleor/products/utils/data"; import { - createAttributeChangeHandler, - createAttributeFileChangeHandler, - createAttributeMultiChangeHandler, - createAttributeReferenceChangeHandler, createChannelsChangeHandler, createChannelsPriceChangeHandler, createProductTypeSelectHandler @@ -35,6 +38,7 @@ import { import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages"; import { SearchProductTypes_search_edges_node } from "@saleor/searches/types/SearchProductTypes"; import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; +import { ReorderEvent } from "@saleor/types"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; @@ -92,6 +96,7 @@ export interface ProductCreateHandlers >, Record<"selectAttributeReference", FormsetChange>, Record<"selectAttributeFile", FormsetChange>, + Record<"reorderAttributeValue", FormsetChange>, Record<"addStock" | "deleteStock", (id: string) => void> { changeDescription: RichTextEditorChange; } @@ -224,6 +229,11 @@ function useProductCreateForm( attributesWithNewFileValue.change, triggerChange ); + const handleAttributeValueReorder = createAttributeValueReorderHandler( + attributes.change, + attributes.data, + triggerChange + ); const handleProductTypeSelect = createProductTypeSelectHandler( attributes.set, setProductType, @@ -305,6 +315,7 @@ function useProductCreateForm( changeMetadata, changeStock: handleStockChange, deleteStock: handleStockDelete, + reorderAttributeValue: handleAttributeValueReorder, selectAttribute: handleAttributeChange, selectAttributeFile: handleAttributeFileChange, selectAttributeMultiple: handleAttributeMultiChange, diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index 1afe7b8e8..87c00d0fd 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -267,6 +267,7 @@ export const ProductUpdatePage: React.FC = ({ onFileChange={handlers.selectAttributeFile} onReferencesRemove={handlers.selectAttributeReference} onReferencesAddClick={onAssignReferencesClick} + onReferencesReorder={handlers.reorderAttributeValue} /> )} diff --git a/src/products/components/ProductUpdatePage/form.tsx b/src/products/components/ProductUpdatePage/form.tsx index b271bf8a8..c49f2c7f1 100644 --- a/src/products/components/ProductUpdatePage/form.tsx +++ b/src/products/components/ProductUpdatePage/form.tsx @@ -1,5 +1,12 @@ import { OutputData } from "@editorjs/editorjs"; import { getAttributesDisplayData } from "@saleor/attributes/utils/data"; +import { + createAttributeChangeHandler, + createAttributeFileChangeHandler, + createAttributeMultiChangeHandler, + createAttributeReferenceChangeHandler, + createAttributeValueReorderHandler +} from "@saleor/attributes/utils/handlers"; import { ChannelData, ChannelPriceArgs } from "@saleor/channels/utils"; import { AttributeInput } from "@saleor/components/Attributes"; import { MetadataFormData } from "@saleor/components/Metadata"; @@ -19,10 +26,6 @@ import { getStockInputFromProduct } from "@saleor/products/utils/data"; import { - createAttributeChangeHandler, - createAttributeFileChangeHandler, - createAttributeMultiChangeHandler, - createAttributeReferenceChangeHandler, createChannelsChangeHandler, createChannelsPriceChangeHandler } from "@saleor/products/utils/handlers"; @@ -32,6 +35,7 @@ import { } from "@saleor/products/utils/validation"; import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages"; import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; +import { ReorderEvent } from "@saleor/types"; import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; @@ -109,6 +113,7 @@ export interface ProductUpdateHandlers >, Record<"selectAttributeReference", FormsetChange>, Record<"selectAttributeFile", FormsetChange>, + Record<"reorderAttributeValue", FormsetChange>, Record<"addStock" | "deleteStock", (id: string) => void> { changeDescription: RichTextEditorChange; } @@ -234,6 +239,11 @@ function useProductUpdateForm( attributesWithNewFileValue.change, triggerChange ); + const handleAttributeValueReorder = createAttributeValueReorderHandler( + attributes.change, + attributes.data, + triggerChange + ); const handleStockChange: FormsetChange = (id, value) => { triggerChange(); stocks.change(id, value); @@ -322,6 +332,7 @@ function useProductUpdateForm( changeMetadata, changeStock: handleStockChange, deleteStock: handleStockDelete, + reorderAttributeValue: handleAttributeValueReorder, selectAttribute: handleAttributeChange, selectAttributeFile: handleAttributeFileChange, selectAttributeMultiple: handleAttributeMultiChange, diff --git a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx index 4354d9ddd..b0caf9591 100644 --- a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx +++ b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx @@ -162,6 +162,9 @@ const ProductVariantCreatePage: React.FC = ({ onChange={handlers.selectAttribute} onMultiChange={handlers.selectAttributeMultiple} onFileChange={handlers.selectAttributeFile} + onReferencesRemove={handlers.selectAttributeReference} + onReferencesAddClick={onAssignReferencesClick} + onReferencesReorder={handlers.reorderAttributeValue} /> = ({ onFileChange={handlers.selectAttributeFile} onReferencesRemove={handlers.selectAttributeReference} onReferencesAddClick={onAssignReferencesClick} + onReferencesReorder={handlers.reorderAttributeValue} /> , Record<"selectAttributeReference", FormsetChange>, Record<"selectAttributeFile", FormsetChange>, + Record<"reorderAttributeValue", FormsetChange>, Record<"addStock" | "deleteStock", (id: string) => void> { changeMetadata: FormChange; } @@ -131,6 +132,11 @@ function useProductVariantCreateForm( attributesWithNewFileValue.change, triggerChange ); + const handleAttributeValueReorder = createAttributeValueReorderHandler( + attributes.change, + attributes.data, + triggerChange + ); const handleStockAdd = (id: string) => { triggerChange(); stocks.add({ @@ -183,6 +189,7 @@ function useProductVariantCreateForm( changeMetadata, changeStock: handleStockChange, deleteStock: handleStockDelete, + reorderAttributeValue: handleAttributeValueReorder, selectAttribute: handleAttributeChange, selectAttributeFile: handleAttributeFileChange, selectAttributeMultiple: handleAttributeMultiChange, diff --git a/src/products/components/ProductVariantPage/ProductVariantPage.tsx b/src/products/components/ProductVariantPage/ProductVariantPage.tsx index 960f64cf8..6e88471a5 100644 --- a/src/products/components/ProductVariantPage/ProductVariantPage.tsx +++ b/src/products/components/ProductVariantPage/ProductVariantPage.tsx @@ -217,6 +217,7 @@ const ProductVariantPage: React.FC = ({ onFileChange={handlers.selectAttributeFile} onReferencesRemove={handlers.selectAttributeReference} onReferencesAddClick={onAssignReferencesClick} + onReferencesReorder={handlers.reorderAttributeValue} /> = ({ onFileChange={handlers.selectAttributeFile} onReferencesRemove={handlers.selectAttributeReference} onReferencesAddClick={onAssignReferencesClick} + onReferencesReorder={handlers.reorderAttributeValue} /> , Record<"selectAttributeReference", FormsetChange>, Record<"selectAttributeFile", FormsetChange>, + Record<"reorderAttributeValue", FormsetChange>, Record<"addStock" | "deleteStock", (id: string) => void> { changeMetadata: FormChange; } @@ -148,6 +149,11 @@ function useProductVariantUpdateForm( attributesWithNewFileValue.change, triggerChange ); + const handleAttributeValueReorder = createAttributeValueReorderHandler( + attributes.change, + attributes.data, + triggerChange + ); const handleStockAdd = (id: string) => { triggerChange(); stocks.add({ @@ -233,6 +239,7 @@ function useProductVariantUpdateForm( changeMetadata, changeStock: handleStockChange, deleteStock: handleStockDelete, + reorderAttributeValue: handleAttributeValueReorder, selectAttribute: handleAttributeChange, selectAttributeFile: handleAttributeFileChange, selectAttributeMultiple: handleAttributeMultiChange, diff --git a/src/products/utils/handlers.test.ts b/src/products/utils/handlers.test.ts deleted file mode 100644 index c01a29da0..000000000 --- a/src/products/utils/handlers.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { AttributeInputData } from "@saleor/components/Attributes"; -import { FormsetData } from "@saleor/hooks/useFormset"; -import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; - -import { createAttributeMultiChangeHandler } from "./handlers"; - -const attributes: FormsetData = [ - { - data: { - inputType: AttributeInputTypeEnum.DROPDOWN, - isRequired: false, - values: [ - { - __typename: "AttributeValue", - file: null, - id: "attrv-1", - name: "Attribute 1 Value 1", - reference: null, - slug: "attr-1-v-1" - } - ] - }, - id: "attr-1", - label: "Attribute 1", - value: [] - }, - { - data: { - inputType: AttributeInputTypeEnum.MULTISELECT, - isRequired: false, - values: [ - { - __typename: "AttributeValue", - file: null, - id: "attrv-2", - name: "Attribute 2 Value 1", - reference: null, - slug: "attr-2-v-1" - }, - { - __typename: "AttributeValue", - file: null, - id: "attrv-3", - name: "Attribute 2 Value 2", - reference: null, - slug: "attr-2-v-2" - }, - { - __typename: "AttributeValue", - file: null, - id: "attrv-4", - name: "Attribute 2 Value 3", - reference: null, - slug: "attr-2-v-3" - } - ] - }, - id: "attr-2", - label: "Attribute 2", - value: ["attr-2-v-3"] - }, - { - data: { - inputType: AttributeInputTypeEnum.FILE, - isRequired: false, - values: [ - { - __typename: "AttributeValue", - file: { - __typename: "File", - contentType: "image/png", - url: "some-non-existing-url" - }, - id: "attrv-5", - name: "File First Value", - reference: null, - slug: "file-first-value" - } - ] - }, - id: "attr-3", - label: "File Attribute", - value: [] - } -]; - -describe("Multiple select", () => { - it("is able to select value", () => { - const change = jest.fn(); - const trigger = jest.fn(); - const handler = createAttributeMultiChangeHandler( - change, - attributes, - trigger - ); - - handler("attr-2", "attr-2-v-1"); - - expect(change).toHaveBeenCalledTimes(1); - expect(change.mock.calls[0][0]).toBe("attr-2"); - expect(change.mock.calls[0][1]).toHaveLength(2); - expect(change.mock.calls[0][1][0]).toBe("attr-2-v-3"); - expect(change.mock.calls[0][1][1]).toBe("attr-2-v-1"); - expect(trigger).toHaveBeenCalledTimes(1); - }); - - it("is able to deselect value", () => { - const change = jest.fn(); - const trigger = jest.fn(); - const handler = createAttributeMultiChangeHandler( - change, - attributes, - trigger - ); - - handler("attr-2", "attr-2-v-3"); - - expect(change).toHaveBeenCalledTimes(1); - expect(change.mock.calls[0][0]).toBe("attr-2"); - expect(change.mock.calls[0][1]).toHaveLength(0); - expect(trigger).toHaveBeenCalledTimes(1); - }); - - it("is able to add custom value", () => { - const change = jest.fn(); - const trigger = jest.fn(); - const handler = createAttributeMultiChangeHandler( - change, - attributes, - trigger - ); - - handler("attr-2", "A Value"); - - expect(change).toHaveBeenCalledTimes(1); - expect(change.mock.calls[0][0]).toBe("attr-2"); - expect(change.mock.calls[0][1]).toHaveLength(2); - expect(change.mock.calls[0][1][0]).toBe("attr-2-v-3"); - expect(change.mock.calls[0][1][1]).toBe("A Value"); - expect(trigger).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/products/utils/handlers.ts b/src/products/utils/handlers.ts index 40e79b80c..00608064c 100644 --- a/src/products/utils/handlers.ts +++ b/src/products/utils/handlers.ts @@ -5,25 +5,10 @@ import { } from "@saleor/channels/utils"; import { AttributeInputData } from "@saleor/components/Attributes"; import { FormChange } from "@saleor/hooks/useForm"; -import { - FormsetAtomicData, - FormsetChange, - FormsetData -} from "@saleor/hooks/useFormset"; -import { toggle } from "@saleor/utils/lists"; +import { FormsetData } from "@saleor/hooks/useFormset"; import { getAttributeInputFromProductType, ProductType } from "./data"; -export function createAttributeChangeHandler( - changeAttributeData: FormsetChange, - triggerChange: () => void -): FormsetChange { - return (attributeId: string, value: string) => { - triggerChange(); - changeAttributeData(attributeId, value === "" ? [] : [value]); - }; -} - export function createChannelsPriceChangeHandler( channelListings: ChannelPriceData[], updateChannels: (data: ChannelPriceData[]) => void, @@ -103,66 +88,6 @@ export function createVariantChannelsChangeHandler( }; } -export function createAttributeMultiChangeHandler( - changeAttributeData: FormsetChange, - attributes: FormsetData, - triggerChange: () => void -): FormsetChange { - return (attributeId: string, value: string) => { - const attribute = attributes.find( - attribute => attribute.id === attributeId - ); - - const newAttributeValues = toggle( - value, - attribute.value, - (a, b) => a === b - ); - - triggerChange(); - changeAttributeData(attributeId, newAttributeValues); - }; -} - -export function createAttributeReferenceChangeHandler( - changeAttributeData: FormsetChange, - triggerChange: () => void -): FormsetChange { - return (attributeId: string, values: string[]) => { - changeAttributeData(attributeId, values); - triggerChange(); - }; -} - -export function createAttributeFileChangeHandler( - changeAttributeData: FormsetChange, - attributesWithNewFileValue: FormsetData>, - addAttributeNewFileValue: (data: FormsetAtomicData) => void, - changeAttributeNewFileValue: FormsetChange, - triggerChange: () => void -): FormsetChange { - return (attributeId: string, value: File) => { - triggerChange(); - - const newFileValueAssigned = attributesWithNewFileValue.find( - attribute => attribute.id === attributeId - ); - - if (newFileValueAssigned) { - changeAttributeNewFileValue(attributeId, value); - } else { - addAttributeNewFileValue({ - data: null, - id: attributeId, - label: null, - value - }); - } - - changeAttributeData(attributeId, value ? [value.name] : []); - }; -} - export function createProductTypeSelectHandler( setAttributes: (data: FormsetData) => void, setProductType: (productType: ProductType) => void, diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index f1661c266..fbd9a13b8 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -11234,6 +11234,389 @@ exports[`Storyshots Generics / Sortable chips field loading 1`] = ` `; +exports[`Storyshots Generics / Sortable chips field with error 1`] = ` +
+
+
+
+
+
+ +
+ Item 1 +
+ +
+
+
+
+ +
+ Item 2 +
+ +
+
+
+
+ +
+ Item 3 +
+ +
+
+
+
+ +
+ Item 4 +
+ +
+
+
+
+ +
+ Item 5 +
+ +
+
+
+
+ +
+ Item 6 +
+ +
+
+
+ Something went wrong +
+
+
+
+
+`; + exports[`Storyshots Generics / Square Button default 1`] = `