fix(dashboard,medusa,types): allow searching for promotion rule options (#12028)

* fix: allow searching for promotion rule options

* fix: allow searching for promotion rule options

* add changeset

* cleanup
This commit is contained in:
Kasper Fabricius Kristensen
2025-03-28 14:45:05 +01:00
committed by GitHub
parent 6d8390a529
commit 3dba58785f
6 changed files with 55 additions and 65 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"@medusajs/dashboard": patch
"@medusajs/types": patch
"@medusajs/medusa": patch
---
fix(dashboard,medusa,types): allow searching for promotion rule options
@@ -197,7 +197,7 @@ const ComboboxImpl = <T extends Value = string>(
const showTag = hasValue && isArrayValue const showTag = hasValue && isArrayValue
const showSelected = showTag && !searchValue && !open const showSelected = showTag && !searchValue && !open
const hideInput = !isArrayValue && !open const hideInput = !isArrayValue && hasValue && !open
const selectedLabel = options.find((o) => o.value === selectedValues)?.label const selectedLabel = options.find((o) => o.value === selectedValues)?.label
const hidePlaceholder = showSelected || open const hidePlaceholder = showSelected || open
@@ -28,6 +28,7 @@ export const useComboboxData = <
defaultValue, defaultValue,
defaultValueKey, defaultValueKey,
pageSize = 10, pageSize = 10,
enabled = true,
}: { }: {
queryKey: QueryKey queryKey: QueryKey
queryFn: (params: TParams) => Promise<TResponse> queryFn: (params: TParams) => Promise<TResponse>
@@ -35,6 +36,7 @@ export const useComboboxData = <
defaultValueKey?: keyof TParams defaultValueKey?: keyof TParams
defaultValue?: string | string[] defaultValue?: string | string[]
pageSize?: number pageSize?: number
enabled?: boolean
}) => { }) => {
const { searchValue, onSearchValueChange, query } = useDebouncedSearch() const { searchValue, onSearchValueChange, query } = useDebouncedSearch()
@@ -47,7 +49,7 @@ export const useComboboxData = <
limit: Array.isArray(defaultValue) ? defaultValue.length : 1, limit: Array.isArray(defaultValue) ? defaultValue.length : 1,
} as TParams) } as TParams)
}, },
enabled: !!defaultValue, enabled: !!defaultValue && enabled,
}) })
const { data, ...rest } = useInfiniteQuery({ const { data, ...rest } = useInfiniteQuery({
@@ -65,6 +67,7 @@ export const useComboboxData = <
return moreItemsExist ? lastPage.offset + lastPage.limit : undefined return moreItemsExist ? lastPage.offset + lastPage.limit : undefined
}, },
placeholderData: keepPreviousData, placeholderData: keepPreviousData,
enabled: enabled,
}) })
const options = data?.pages.flatMap((page) => getOptions(page)) ?? [] const options = data?.pages.flatMap((page) => getOptions(page)) ?? []
@@ -74,7 +77,8 @@ export const useComboboxData = <
* If there are no options and the query is empty, then the combobox should be disabled, * If there are no options and the query is empty, then the combobox should be disabled,
* as there is no data to search for. * as there is no data to search for.
*/ */
const disabled = !rest.isPending && !options.length && !searchValue const disabled =
(!rest.isPending && !options.length && !searchValue) || !enabled
// make sure that the default value is included in the options // make sure that the default value is included in the options
if (defaultValue && defaultOptions.length && !searchValue) { if (defaultValue && defaultOptions.length && !searchValue) {
@@ -1,10 +1,11 @@
import { RuleAttributeOptionsResponse, StoreDTO } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { Input, Select } from "@medusajs/ui" import { Input } from "@medusajs/ui"
import { useWatch } from "react-hook-form" import { useWatch } from "react-hook-form"
import { Form } from "../../../../../../components/common/form" import { Form } from "../../../../../../components/common/form"
import { Combobox } from "../../../../../../components/inputs/combobox" import { Combobox } from "../../../../../../components/inputs/combobox"
import { usePromotionRuleValues } from "../../../../../../hooks/api/promotions"
import { useStore } from "../../../../../../hooks/api/store" import { useStore } from "../../../../../../hooks/api/store"
import { useComboboxData } from "../../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../../lib/client"
type RuleValueFormFieldType = { type RuleValueFormFieldType = {
form: any form: any
@@ -16,11 +17,11 @@ type RuleValueFormFieldType = {
name: string name: string
operator: string operator: string
fieldRule: any fieldRule: any
attributes: RuleAttributeOptionsResponse[] attributes: HttpTypes.AdminRuleAttributeOption[]
ruleType: "rules" | "target-rules" | "buy-rules" ruleType: "rules" | "target-rules" | "buy-rules"
} }
const buildFilters = (attribute?: string, store?: StoreDTO) => { const buildFilters = (attribute?: string, store?: HttpTypes.AdminStore) => {
if (!attribute || !store) { if (!attribute || !store) {
return {} return {}
} }
@@ -49,17 +50,25 @@ export const RuleValueFormField = ({
) )
const { store, isLoading: isStoreLoading } = useStore() const { store, isLoading: isStoreLoading } = useStore()
const { values: options = [] } = usePromotionRuleValues(
ruleType, const comboboxData = useComboboxData({
attribute?.id!, queryFn: async (params) => {
buildFilters(attribute?.id, store), return await sdk.admin.promotion.listRuleValues(
{ ruleType,
enabled: attribute?.id!,
!!attribute?.id && {
["select", "multiselect"].includes(attribute.field_type) && ...params,
!isStoreLoading, ...buildFilters(attribute?.id, store!),
} }
) )
},
enabled:
!!attribute?.id &&
["select", "multiselect"].includes(attribute.field_type) &&
!isStoreLoading,
getOptions: (data) => data.values,
queryKey: ["rule-value-options", ruleType, attribute?.id],
})
const watchOperator = useWatch({ const watchOperator = useWatch({
control: form.control, control: form.control,
@@ -103,54 +112,21 @@ export const RuleValueFormField = ({
<Form.ErrorMessage /> <Form.ErrorMessage />
</Form.Item> </Form.Item>
) )
} else if (watchOperator === "eq") {
return (
<Form.Item className="basis-1/2">
<Form.Control>
<Select
{...field}
value={
Array.isArray(field.value) ? field.value[0] : field.value
}
onValueChange={onChange}
disabled={!fieldRule.attribute}
>
<Select.Trigger ref={ref} className="bg-ui-bg-base">
<Select.Value placeholder="Select Value" />
</Select.Trigger>
<Select.Content>
{options?.map((option, i) => (
<Select.Item
key={`${identifier}-value-option-${i}`}
value={option.value}
>
<span className="text-ui-fg-subtle">
{option.label}
</span>
</Select.Item>
))}
</Select.Content>
</Select>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
} else { } else {
return ( return (
<Form.Item className="basis-1/2"> <Form.Item className="basis-1/2">
<Form.Control> <Form.Control>
<Combobox <Combobox
{...field} {...field}
{...comboboxData}
multiple={watchOperator !== "eq"}
ref={ref} ref={ref}
placeholder="Select Values" placeholder={
options={options} watchOperator === "eq" ? "Select Value" : "Select Values"
}
onChange={onChange} onChange={onChange}
className="bg-ui-bg-base"
disabled={!fieldRule.attribute}
/> />
</Form.Control> </Form.Control>
<Form.ErrorMessage /> <Form.ErrorMessage />
</Form.Item> </Form.Item>
) )
@@ -61,12 +61,12 @@ export interface RuleValueOptionsResponse {
/** /**
* @experimental * @experimental
*/ */
export type AdminRuleValueOptionsListResponse = { export type AdminRuleValueOptionsListResponse = PaginatedResponse<{
/** /**
* The list of rule value options. * The list of rule value options.
*/ */
values: AdminRuleValueOption[] values: AdminRuleValueOption[]
} }>
export type AdminPromotionRuleBatchResponse = BatchResponse<AdminPromotionRule> export type AdminPromotionRuleBatchResponse = BatchResponse<AdminPromotionRule>
@@ -1,18 +1,18 @@
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/framework/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "@medusajs/framework/http" } from "@medusajs/framework/http"
import { HttpTypes } from "@medusajs/framework/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/framework/utils"
import { import {
ruleQueryConfigurations, ruleQueryConfigurations,
validateRuleAttribute, validateRuleAttribute,
validateRuleType, validateRuleType,
} from "../../../utils" } from "../../../utils"
import { AdminGetPromotionRuleParamsType } from "../../../validators" import { AdminGetPromotionRuleParamsType } from "../../../validators"
import { HttpTypes } from "@medusajs/framework/types"
/* /*
This endpoint returns all the potential values for rules (promotion rules, target rules and buy rules) This endpoint returns all the potential values for rules (promotion rules, target rules and buy rules)
@@ -49,7 +49,7 @@ export const GET = async (
applicationMethodType, applicationMethodType,
}) })
const { rows } = await remoteQuery( const { rows, metadata } = await remoteQuery(
remoteQueryObjectFromString({ remoteQueryObjectFromString({
entryPoint: queryConfig.entryPoint, entryPoint: queryConfig.entryPoint,
variables: { variables: {
@@ -67,5 +67,8 @@ export const GET = async (
res.json({ res.json({
values, values,
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
}) })
} }