import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import IconButton from "@material-ui/core/IconButton"; import makeStyles from "@material-ui/core/styles/makeStyles"; import Typography from "@material-ui/core/Typography"; import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown"; import CardTitle from "@saleor/components/CardTitle"; import Hr from "@saleor/components/Hr"; import MultiAutocompleteSelectField, { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; import SingleAutocompleteSelectField, { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField"; import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFragment"; import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment"; 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 { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { getProductErrorMessage } from "@saleor/utils/errors"; import getPageErrorMessage from "@saleor/utils/errors/page"; import classNames from "classnames"; import React from "react"; import { defineMessages, FormattedMessage, IntlShape, useIntl } from "react-intl"; import FileUploadField, { FileChoiceType } from "../FileUploadField"; import SortableChipsField, { SortableChipsFieldValueType } from "../SortableChipsField"; import BasicAttributeRow from "./BasicAttributeRow"; import ExtendedAttributeRow from "./ExtendedAttributeRow"; import { VariantAttributeScope } from "./types"; export interface AttributeInputData { inputType: AttributeInputTypeEnum; variantAttributeScope?: VariantAttributeScope; isRequired: boolean; values: AttributeValueFragment[]; selectedValues?: AttributeValueFragment[]; references?: SearchPages_search_edges_node[]; } export type AttributeInput = FormsetAtomicData; export type AttributeFileInput = FormsetAtomicData; export interface AttributesProps { attributes: AttributeInput[]; disabled: boolean; loading: boolean; errors: Array< 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 } const useStyles = makeStyles( theme => ({ attributeSection: { "&:last-of-type": { paddingBottom: 0 }, padding: theme.spacing(2, 0) }, attributeSectionLabel: { alignItems: "center", display: "flex" }, card: { overflow: "visible" }, cardContent: { "&:last-child": { paddingBottom: theme.spacing(1) }, paddingTop: theme.spacing(1) }, expansionBar: { display: "flex" }, expansionBarButton: { marginBottom: theme.spacing(1) }, expansionBarButtonIcon: { transition: theme.transitions.duration.short + "ms" }, expansionBarLabel: { color: theme.palette.text.disabled, fontSize: 14 }, expansionBarLabelContainer: { alignItems: "center", display: "flex", flex: 1 }, fileField: { float: "right" }, rotate: { transform: "rotate(180deg)" }, uploadFileButton: { float: "right" }, uploadFileContent: { color: theme.palette.primary.main, float: "right", fontSize: "1rem" } }), { name: "Attributes" } ); function getMultiChoices( values: AttributeValueFragment[] ): MultiAutocompleteChoiceType[] { return values.map(value => ({ label: value.name, value: value.slug })); } function getMultiDisplayValue( attribute: AttributeInput ): MultiAutocompleteChoiceType[] { if (!attribute.value) { return []; } return attribute.value.map(attributeValue => { const definedAttributeValue = attribute.data.values.find( definedValue => definedValue.slug === attributeValue ); if (!!definedAttributeValue) { return { label: definedAttributeValue.name, value: definedAttributeValue.slug }; } return { label: attributeValue, value: attributeValue }; }); } function getReferenceDisplayValue( attribute: AttributeInput ): SortableChipsFieldValueType[] { if (!attribute.value) { return []; } return attribute.value.map(attributeValue => { const definedAttributeValue = attribute.data.values.find( definedValue => definedValue.reference === attributeValue ); // If value has been previously assigned, use it's data if (!!definedAttributeValue) { return { label: definedAttributeValue.name, value: definedAttributeValue.reference }; } const definedAttributeReference = attribute.data.references?.find( reference => reference.id === attributeValue ); // If value has not been yet assigned, use data of reference if (!!definedAttributeReference) { return { label: definedAttributeReference.title, value: definedAttributeReference.id }; } return { label: attributeValue, value: attributeValue }; }); } function getSingleChoices( values: AttributeValueFragment[] ): SingleAutocompleteChoiceType[] { return values.map(value => ({ label: value.name, value: value.slug })); } function getFileChoice(attribute: AttributeInput): FileChoiceType { const attributeValue = attribute.value?.length > 0 && attribute.value[0]; const definedAttributeValue = attribute.data.values.find( definedValue => definedValue.slug === attributeValue ); if (definedAttributeValue) { return { file: definedAttributeValue.file, label: definedAttributeValue.name, value: definedAttributeValue.slug }; } return { label: attributeValue, value: attributeValue }; } const messages = defineMessages({ attributesNumber: { defaultMessage: "{number} Attributes", description: "number of attributes" }, header: { defaultMessage: "Attributes", description: "attributes, section header" }, multipleValueLable: { defaultMessage: "Values", description: "attribute values" }, valueLabel: { defaultMessage: "Value", description: "attribute value" } }); function getErrorMessage( err: ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment, intl: IntlShape ): string { switch (err?.__typename) { case "ProductError": return getProductErrorMessage(err, intl); case "PageError": return getPageErrorMessage(err, intl); } } const Attributes: React.FC = ({ attributes, disabled, loading, errors, title, onChange, onMultiChange, onFileChange, onReferencesRemove, onReferencesAddClick, onReferencesReorder }) => { const intl = useIntl(); const classes = useStyles({}); const [expanded, setExpansionStatus] = React.useState(true); const toggleExpansion = () => setExpansionStatus(!expanded); return (
{expanded && attributes.length > 0 && ( <>
{attributes.map((attribute, attributeIndex) => { const error = errors.find(err => err.attributes?.includes(attribute.id) ); return ( {attributeIndex > 0 &&
} {attribute.data.inputType === AttributeInputTypeEnum.REFERENCE ? ( onReferencesAddClick(attribute)} disabled={disabled} > onReferencesRemove( attribute.id, attribute.value?.filter(id => id !== value) ) } onValueReorder={onReferencesReorder} loading={loading} /> ) : attribute.data.inputType === AttributeInputTypeEnum.FILE ? ( onFileChange(attribute.id, file)} onFileDelete={() => onFileChange(attribute.id, undefined) } error={!!error} helperText={getErrorMessage(error, intl)} inputProps={{ name: `attribute:${attribute.label}` }} /> ) : attribute.data.inputType === AttributeInputTypeEnum.DROPDOWN ? ( value.slug === attribute.value[0] )?.name || attribute.value[0] || "" } emptyOption={!attribute.data.isRequired} error={!!error} helperText={getErrorMessage(error, intl)} name={`attribute:${attribute.label}`} label={intl.formatMessage(messages.valueLabel)} value={attribute.value[0]} onChange={event => onChange(attribute.id, event.target.value) } allowCustomValues={!attribute.data.isRequired} /> ) : ( onMultiChange(attribute.id, event.target.value) } allowCustomValues={!attribute.data.isRequired} /> )}
); })} )}
); }; Attributes.displayName = "Attributes"; export default Attributes;