import { XMarkMini } from "@medusajs/icons" import { AdminPromotion, HttpTypes } from "@medusajs/types" import { Badge, Button, Heading, IconButton, Select, Text } from "@medusajs/ui" import { forwardRef, Fragment, useEffect, useRef } from "react" import { Control, ControllerRenderProps, useFieldArray, useWatch, } from "react-hook-form" import type { CreatePromotionSchemaType } from "../../../../promotion-create/components/create-promotion-form/form-schema" import { useTranslation } from "react-i18next" import { Form } from "../../../../../../components/common/form" import { usePromotionRuleAttributes, usePromotionRules, } from "../../../../../../hooks/api/promotions" import { useDocumentDirection } from "../../../../../../hooks/use-document-direction" import { generateRuleAttributes } from "../edit-rules-form/utils" import { RuleValueFormField } from "../rule-value-form-field" import { requiredProductRule } from "./constants" type RulesFormFieldForm = { control: Control getValues: () => CreatePromotionSchemaType resetField: ( name: | "rules" | "application_method.buy_rules" | "application_method.target_rules" ) => void } type RulesFormFieldType = { promotion?: AdminPromotion form: RulesFormFieldForm ruleType: "rules" | "target-rules" | "buy-rules" setRulesToRemove?: any rulesToRemove?: any scope?: | "application_method.buy_rules" | "rules" | "application_method.target_rules" formType?: "create" | "edit" } export type RuleWithFormAttributes = Omit< HttpTypes.AdminPromotionRule, "values" > & { values: { value: string }[] required?: boolean field_type?: string disguised?: boolean } export const RulesFormField = ({ form, ruleType, setRulesToRemove, rulesToRemove, scope = "rules", promotion, formType = "create", }: RulesFormFieldType) => { const initialRulesSet = useRef(false) const { t } = useTranslation() const direction = useDocumentDirection() const formData = form.getValues() const { attributes } = usePromotionRuleAttributes( ruleType, formData.type, formData.application_method?.target_type ) as { attributes: HttpTypes.AdminRuleAttributeOption[] | undefined } const { fields, append, remove, update, replace } = useFieldArray({ control: form.control, name: scope, keyName: scope, }) const promotionType = useWatch({ control: form.control, name: "type", defaultValue: promotion?.type, }) const applicationMethodType = useWatch({ control: form.control, name: "application_method.type", defaultValue: promotion?.application_method?.type, }) const applicationMethodTargetType = useWatch({ control: form.control, name: "application_method.target_type", defaultValue: promotion?.application_method?.target_type, }) const query: Record = promotionType ? { promotion_type: promotionType, application_method_type: applicationMethodType, application_method_target_type: applicationMethodTargetType, } : {} const { rules, isLoading } = usePromotionRules( promotion?.id || null, ruleType, query, { enabled: !!promotion?.id || (!!promotionType && !!applicationMethodType), } ) useEffect(() => { if (isLoading) { return } /** * This effect sets rules after mount but since it is reused in create and edit flows, prevent this hook from recreating rules * when fields are intentionally set to empty (e.g. "Clear all" is pressed). */ if (!fields.length && formType === "edit" && initialRulesSet.current) { return } if (ruleType === "rules" && !fields.length) { form.resetField("rules") replace( generateRuleAttributes(rules as unknown as RuleWithFormAttributes[]) ) } if (ruleType === "buy-rules" && !fields.length) { form.resetField("application_method.buy_rules") const rulesToAppend = promotion?.id || promotionType === "standard" ? rules : [...(rules ?? []), requiredProductRule] replace(generateRuleAttributes(rulesToAppend as RuleWithFormAttributes[])) } if (ruleType === "target-rules" && !fields.length) { form.resetField("application_method.target_rules") const rulesToAppend = promotion?.id || promotionType === "standard" ? rules : [...(rules ?? []), requiredProductRule] replace(generateRuleAttributes(rulesToAppend as RuleWithFormAttributes[])) } initialRulesSet.current = true }, [ promotionType, isLoading, ruleType, fields.length, formType, form, replace, rules, promotion?.id, ]) return (
{t( ruleType === "target-rules" ? (`promotions.fields.conditions.${ruleType}.${applicationMethodTargetType}.title` as any) : (`promotions.fields.conditions.${ruleType}.title` as any) )} {t( ruleType === "target-rules" ? (`promotions.fields.conditions.${ruleType}.${applicationMethodTargetType}.description` as any) : (`promotions.fields.conditions.${ruleType}.description` as any) )} {fields.map((fieldRule, index) => { const identifier = fieldRule.id return (
{ const { onChange, ref, ...fieldProps } = field const existingAttributes = fields?.map((field: any) => field.attribute) || [] const attributeOptions = attributes?.filter((attr) => { if (attr.value === fieldRule.attribute) { return true } return !existingAttributes.includes(attr.value) }) || [] const disabled = !!fieldRule.required const onValueChange = (e: string) => { const currentAttributeOption = attributeOptions.find( (ao) => ao.id === e ) const fieldRuleOverrides: typeof fieldRule = { ...fieldRule, disguised: currentAttributeOption?.disguised || false, } if (currentAttributeOption?.operators?.length === 1) { fieldRuleOverrides.operator = currentAttributeOption.operators[0].value } if (fieldRuleOverrides.operator === "eq") { fieldRuleOverrides.values = "" } else { fieldRuleOverrides.values = [] } update(index, fieldRuleOverrides) onChange(e) } return ( {fieldRule.required && (

{t("promotions.form.required")}

)} {!disabled ? ( ) : ( ao.value === fieldRule.attribute )?.label || "" } field={field} /> )}
) }} />
{ const { onChange, ref, ...fieldProps } = field const currentAttributeOption = attributes?.find( (attr) => attr.value === fieldRule.attribute ) const options = currentAttributeOption?.operators?.map((o, idx) => ({ label: o.label, value: o.value, key: `${identifier}-operator-option-${idx}`, })) || [] const disabled = !!fieldRule.attribute && options?.length <= 1 return ( {!disabled ? ( ) : ( o.value === fieldProps.value )?.label || "" } field={field} /> )} ) }} />
{!fieldRule.required && ( { if (!fieldRule.required) { setRulesToRemove && setRulesToRemove([...rulesToRemove, fieldRule]) remove(index) } }} > )}
{index < fields.length - 1 && (
{t("promotions.form.and")}
)}
) })}
{!!fields.length && ( )}
) } type DisabledAttributeProps = { label: string field: ControllerRenderProps } /** * Render this if an attribute is disabled, or * if there is only one option available. */ const DisabledField = forwardRef( ({ label, field }, ref) => { return (
{label}
) } ) DisabledField.displayName = "DisabledField"