fix(dashboard): Paginate Comboboxes in create and edit organization forms (#7602)
**What** - Paginates comboboxes - Loads categories relation on details page. - Fix the endpoint used by client to fetch product tags (temp until we add sdk methods) Resolves CORE-2073
This commit is contained in:
@@ -200,8 +200,6 @@
|
|||||||
"attributes": "Attributes",
|
"attributes": "Attributes",
|
||||||
"editProduct": "Edit Product",
|
"editProduct": "Edit Product",
|
||||||
"editAttributes": "Edit Attributes",
|
"editAttributes": "Edit Attributes",
|
||||||
"organization": "Organize",
|
|
||||||
"editOrganization": "Edit Organization",
|
|
||||||
"editOptions": "Edit Options",
|
"editOptions": "Edit Options",
|
||||||
"editPrices": "Edit prices",
|
"editPrices": "Edit prices",
|
||||||
"media": {
|
"media": {
|
||||||
@@ -338,6 +336,15 @@
|
|||||||
"successToast": "Option {{title}} was successfully created."
|
"successToast": "Option {{title}} was successfully created."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"organization": {
|
||||||
|
"header": "Organize",
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit Organization",
|
||||||
|
"toasts": {
|
||||||
|
"success": "Successfully updated the organization of {{title}}."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"success": {
|
"success": {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const client = {
|
|||||||
salesChannels: salesChannels,
|
salesChannels: salesChannels,
|
||||||
shippingOptions: shippingOptions,
|
shippingOptions: shippingOptions,
|
||||||
shippingProfiles: shippingProfiles,
|
shippingProfiles: shippingProfiles,
|
||||||
tags: tags,
|
productTags: tags,
|
||||||
users: users,
|
users: users,
|
||||||
orders: orders,
|
orders: orders,
|
||||||
taxes: taxes,
|
taxes: taxes,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { getRequest } from "./common"
|
import { getRequest } from "./common"
|
||||||
|
|
||||||
async function listProductTags(query?: Record<string, any>) {
|
async function listProductTags(query?: Record<string, any>) {
|
||||||
return getRequest<any>(`/admin/tags`, query)
|
return getRequest<any>(`/admin/product-tags`, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function retrieveProductTag(id: string, query?: Record<string, any>) {
|
async function retrieveProductTag(id: string, query?: Record<string, any>) {
|
||||||
return getRequest<any>(`/admin/tags/${id}`, query)
|
return getRequest<any>(`/admin/product-tags/${id}`, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tags = {
|
export const tags = {
|
||||||
|
|||||||
+1
-1
@@ -156,7 +156,7 @@ export const CategoryCombobox = forwardRef<
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover.Root open={open} onOpenChange={handleOpenChange}>
|
<Popover.Root modal open={open} onOpenChange={handleOpenChange}>
|
||||||
<Popover.Trigger asChild>
|
<Popover.Trigger asChild>
|
||||||
<div
|
<div
|
||||||
className={clx(
|
className={clx(
|
||||||
|
|||||||
+41
-3
@@ -41,6 +41,16 @@ export const ProductCreateOrganizationSection = ({
|
|||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const tags = useComboboxData({
|
||||||
|
queryKey: ["product_tags"],
|
||||||
|
queryFn: client.productTags.list,
|
||||||
|
getOptions: (data) =>
|
||||||
|
data.product_tags.map((tag) => ({
|
||||||
|
label: tag.value,
|
||||||
|
value: tag.id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
const { fields, remove, replace } = useFieldArray({
|
const { fields, remove, replace } = useFieldArray({
|
||||||
control: form.control,
|
control: form.control,
|
||||||
name: "sales_channels",
|
name: "sales_channels",
|
||||||
@@ -53,7 +63,7 @@ export const ProductCreateOrganizationSection = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="organize" className="flex flex-col gap-y-8">
|
<div id="organize" className="flex flex-col gap-y-8">
|
||||||
<Heading>{t("products.organization")}</Heading>
|
<Heading>{t("products.organization.header")}</Heading>
|
||||||
<div className="grid grid-cols-1 gap-x-4">
|
<div className="grid grid-cols-1 gap-x-4">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -84,7 +94,7 @@ export const ProductCreateOrganizationSection = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-x-4">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="type_id"
|
name="type_id"
|
||||||
@@ -100,8 +110,10 @@ export const ProductCreateOrganizationSection = ({
|
|||||||
options={types.options}
|
options={types.options}
|
||||||
searchValue={types.searchValue}
|
searchValue={types.searchValue}
|
||||||
onSearchValueChange={types.onSearchValueChange}
|
onSearchValueChange={types.onSearchValueChange}
|
||||||
|
fetchNextPage={types.fetchNextPage}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -121,14 +133,16 @@ export const ProductCreateOrganizationSection = ({
|
|||||||
options={collections.options}
|
options={collections.options}
|
||||||
searchValue={collections.searchValue}
|
searchValue={collections.searchValue}
|
||||||
onSearchValueChange={collections.onSearchValueChange}
|
onSearchValueChange={collections.onSearchValueChange}
|
||||||
|
fetchNextPage={collections.fetchNextPage}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-x-4">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="categories"
|
name="categories"
|
||||||
@@ -141,6 +155,30 @@ export const ProductCreateOrganizationSection = ({
|
|||||||
<Form.Control>
|
<Form.Control>
|
||||||
<CategoryCombobox {...field} />
|
<CategoryCombobox {...field} />
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="tags"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>
|
||||||
|
{t("products.fields.tags.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Combobox
|
||||||
|
{...field}
|
||||||
|
options={tags.options}
|
||||||
|
searchValue={tags.searchValue}
|
||||||
|
onSearchValueChange={tags.onSearchValueChange}
|
||||||
|
fetchNextPage={tags.fetchNextPage}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ export const ProductOrganizationSection = ({
|
|||||||
return (
|
return (
|
||||||
<Container className="divide-y p-0">
|
<Container className="divide-y p-0">
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<Heading level="h2">{t("products.organization")}</Heading>
|
<Heading level="h2">{t("products.organization.header")}</Heading>
|
||||||
<ActionMenu
|
<ActionMenu
|
||||||
groups={[
|
groups={[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const PRODUCT_DETAIL_FIELDS = "*categories"
|
||||||
@@ -3,10 +3,12 @@ import { LoaderFunctionArgs } from "react-router-dom"
|
|||||||
import { productsQueryKeys } from "../../../hooks/api/products"
|
import { productsQueryKeys } from "../../../hooks/api/products"
|
||||||
import { client } from "../../../lib/client"
|
import { client } from "../../../lib/client"
|
||||||
import { queryClient } from "../../../lib/query-client"
|
import { queryClient } from "../../../lib/query-client"
|
||||||
|
import { PRODUCT_DETAIL_FIELDS } from "./constants"
|
||||||
|
|
||||||
const productDetailQuery = (id: string) => ({
|
const productDetailQuery = (id: string) => ({
|
||||||
queryKey: productsQueryKeys.detail(id),
|
queryKey: productsQueryKeys.detail(id),
|
||||||
queryFn: async () => client.products.retrieve(id),
|
queryFn: async () =>
|
||||||
|
client.products.retrieve(id, { fields: PRODUCT_DETAIL_FIELDS }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const productLoader = async ({ params }: LoaderFunctionArgs) => {
|
export const productLoader = async ({ params }: LoaderFunctionArgs) => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { ProductOptionSection } from "./components/product-option-section"
|
|||||||
import { ProductOrganizationSection } from "./components/product-organization-section"
|
import { ProductOrganizationSection } from "./components/product-organization-section"
|
||||||
import { ProductSalesChannelSection } from "./components/product-sales-channel-section"
|
import { ProductSalesChannelSection } from "./components/product-sales-channel-section"
|
||||||
import { ProductVariantSection } from "./components/product-variant-section"
|
import { ProductVariantSection } from "./components/product-variant-section"
|
||||||
|
import { PRODUCT_DETAIL_FIELDS } from "./constants"
|
||||||
import { productLoader } from "./loader"
|
import { productLoader } from "./loader"
|
||||||
|
|
||||||
import after from "virtual:medusa/widgets/product/details/after"
|
import after from "virtual:medusa/widgets/product/details/after"
|
||||||
@@ -16,16 +17,19 @@ import before from "virtual:medusa/widgets/product/details/before"
|
|||||||
import sideAfter from "virtual:medusa/widgets/product/details/side/after"
|
import sideAfter from "virtual:medusa/widgets/product/details/side/after"
|
||||||
import sideBefore from "virtual:medusa/widgets/product/details/side/before"
|
import sideBefore from "virtual:medusa/widgets/product/details/side/before"
|
||||||
|
|
||||||
// TODO: Use product domain translations only
|
|
||||||
export const ProductDetail = () => {
|
export const ProductDetail = () => {
|
||||||
const initialData = useLoaderData() as Awaited<
|
const initialData = useLoaderData() as Awaited<
|
||||||
ReturnType<typeof productLoader>
|
ReturnType<typeof productLoader>
|
||||||
>
|
>
|
||||||
|
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const { product, isLoading, isError, error } = useProduct(id!, undefined, {
|
const { product, isLoading, isError, error } = useProduct(
|
||||||
initialData: initialData,
|
id!,
|
||||||
})
|
{ fields: PRODUCT_DETAIL_FIELDS },
|
||||||
|
{
|
||||||
|
initialData: initialData,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (isLoading || !product) {
|
if (isLoading || !product) {
|
||||||
return <div>Loading...</div>
|
return <div>Loading...</div>
|
||||||
|
|||||||
+83
-70
@@ -1,6 +1,6 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { Button, Select } from "@medusajs/ui"
|
import { Button, toast } from "@medusajs/ui"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
@@ -10,11 +10,10 @@ import {
|
|||||||
RouteDrawer,
|
RouteDrawer,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/route-modal"
|
} from "../../../../../components/route-modal"
|
||||||
import { useCategories } from "../../../../../hooks/api/categories"
|
|
||||||
import { useCollections } from "../../../../../hooks/api/collections"
|
|
||||||
import { useProductTypes } from "../../../../../hooks/api/product-types"
|
|
||||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||||
import { useTags } from "../../../../../hooks/api/tags"
|
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
|
||||||
|
import { client, sdk } from "../../../../../lib/client"
|
||||||
|
import { CategoryCombobox } from "../../../common/components/category-combobox"
|
||||||
|
|
||||||
type ProductOrganizationFormProps = {
|
type ProductOrganizationFormProps = {
|
||||||
product: Product
|
product: Product
|
||||||
@@ -23,8 +22,8 @@ type ProductOrganizationFormProps = {
|
|||||||
const ProductOrganizationSchema = zod.object({
|
const ProductOrganizationSchema = zod.object({
|
||||||
type_id: zod.string().optional(),
|
type_id: zod.string().optional(),
|
||||||
collection_id: zod.string().optional(),
|
collection_id: zod.string().optional(),
|
||||||
category_ids: zod.array(zod.string()).optional(),
|
category_ids: zod.array(zod.string()),
|
||||||
tags: zod.array(zod.string()).optional(),
|
tag_ids: zod.array(zod.string()),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const ProductOrganizationForm = ({
|
export const ProductOrganizationForm = ({
|
||||||
@@ -32,38 +31,76 @@ export const ProductOrganizationForm = ({
|
|||||||
}: ProductOrganizationFormProps) => {
|
}: ProductOrganizationFormProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleSuccess } = useRouteModal()
|
const { handleSuccess } = useRouteModal()
|
||||||
const { product_types, isLoading: isLoadingTypes } = useProductTypes()
|
|
||||||
const { product_tags, isLoading: isLoadingTags } = useTags()
|
const collections = useComboboxData({
|
||||||
const { collections, isLoading: isLoadingCollections } = useCollections()
|
queryKey: ["product_collections"],
|
||||||
const { product_categories, isLoading: isLoadingCategories } = useCategories()
|
queryFn: sdk.admin.collection.list,
|
||||||
|
getOptions: (data) =>
|
||||||
|
data.collections.map((collection) => ({
|
||||||
|
label: collection.title!,
|
||||||
|
value: collection.id!,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
|
const types = useComboboxData({
|
||||||
|
queryKey: ["product_types"],
|
||||||
|
queryFn: client.productTypes.list,
|
||||||
|
getOptions: (data) =>
|
||||||
|
data.product_types.map((type) => ({
|
||||||
|
label: type.value,
|
||||||
|
value: type.id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
|
const tags = useComboboxData({
|
||||||
|
queryKey: ["product_tags"],
|
||||||
|
queryFn: client.productTags.list,
|
||||||
|
getOptions: (data) =>
|
||||||
|
data.product_tags.map((tag) => ({
|
||||||
|
label: tag.value,
|
||||||
|
value: tag.id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
const form = useForm<zod.infer<typeof ProductOrganizationSchema>>({
|
const form = useForm<zod.infer<typeof ProductOrganizationSchema>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
type_id: product.type_id || undefined,
|
type_id: product.type_id || "",
|
||||||
collection_id: product.collection_id || undefined,
|
collection_id: product.collection_id || "",
|
||||||
category_ids: product.categories?.map((c) => c.id) || undefined,
|
category_ids: product.categories?.map((c) => c.id) || [],
|
||||||
tags: product.tags?.map((t) => t.id) || undefined,
|
tag_ids: product.tags?.map((t) => t.id) || [],
|
||||||
},
|
},
|
||||||
resolver: zodResolver(ProductOrganizationSchema),
|
resolver: zodResolver(ProductOrganizationSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = useUpdateProduct(product.id)
|
const { mutateAsync, isPending } = useUpdateProduct(product.id)
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{
|
{
|
||||||
type_id: data.type_id || undefined,
|
type_id: data.type_id || undefined,
|
||||||
collection_id: data.collection_id || undefined,
|
collection_id: data.collection_id || undefined,
|
||||||
category_ids: data.category_ids || undefined,
|
categories: data.category_ids.map((id) => ({ id })) || undefined,
|
||||||
tags:
|
tags:
|
||||||
data.tags?.map((t) => {
|
data.tag_ids?.map((t) => {
|
||||||
t
|
t
|
||||||
}) || undefined,
|
}) || undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: ({ product }) => {
|
||||||
|
toast.success(t("general.success"), {
|
||||||
|
description: t("products.organization.edit.toasts.success", {
|
||||||
|
title: product.title,
|
||||||
|
}),
|
||||||
|
})
|
||||||
handleSuccess()
|
handleSuccess()
|
||||||
},
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(t("general.error"), {
|
||||||
|
description: error.message,
|
||||||
|
dismissable: true,
|
||||||
|
dismissLabel: t("actions.close"),
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -72,34 +109,26 @@ export const ProductOrganizationForm = ({
|
|||||||
<RouteDrawer.Form form={form}>
|
<RouteDrawer.Form form={form}>
|
||||||
<form onSubmit={handleSubmit} className="flex h-full flex-col">
|
<form onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||||
<RouteDrawer.Body>
|
<RouteDrawer.Body>
|
||||||
<div className="flex h-full flex-col gap-y-8">
|
<div className="flex h-full flex-col gap-y-4">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="type_id"
|
name="type_id"
|
||||||
render={({ field: { onChange, ...field } }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label optional>
|
<Form.Label optional>
|
||||||
{t("products.fields.type.label")}
|
{t("products.fields.type.label")}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Select
|
<Combobox
|
||||||
disabled={isLoadingTypes}
|
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={onChange}
|
options={types.options}
|
||||||
>
|
searchValue={types.searchValue}
|
||||||
<Select.Trigger ref={field.ref}>
|
onSearchValueChange={types.onSearchValueChange}
|
||||||
<Select.Value />
|
fetchNextPage={types.fetchNextPage}
|
||||||
</Select.Trigger>
|
/>
|
||||||
<Select.Content>
|
|
||||||
{(product_types ?? []).map((type) => (
|
|
||||||
<Select.Item key={type.id} value={type.id}>
|
|
||||||
{type.value}
|
|
||||||
</Select.Item>
|
|
||||||
))}
|
|
||||||
</Select.Content>
|
|
||||||
</Select>
|
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -107,33 +136,22 @@ export const ProductOrganizationForm = ({
|
|||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="collection_id"
|
name="collection_id"
|
||||||
render={({ field: { onChange, ...field } }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label optional>
|
<Form.Label optional>
|
||||||
{t("products.fields.collection.label")}
|
{t("products.fields.collection.label")}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Select
|
<Combobox
|
||||||
disabled={isLoadingCollections}
|
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={onChange}
|
options={collections.options}
|
||||||
>
|
searchValue={collections.searchValue}
|
||||||
<Select.Trigger ref={field.ref}>
|
onSearchValueChange={collections.onSearchValueChange}
|
||||||
<Select.Value />
|
fetchNextPage={collections.fetchNextPage}
|
||||||
</Select.Trigger>
|
/>
|
||||||
<Select.Content>
|
|
||||||
{(collections ?? []).map((collection) => (
|
|
||||||
<Select.Item
|
|
||||||
key={collection.id}
|
|
||||||
value={collection.id}
|
|
||||||
>
|
|
||||||
{collection.title}
|
|
||||||
</Select.Item>
|
|
||||||
))}
|
|
||||||
</Select.Content>
|
|
||||||
</Select>
|
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -148,22 +166,16 @@ export const ProductOrganizationForm = ({
|
|||||||
{t("products.fields.categories.label")}
|
{t("products.fields.categories.label")}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Combobox
|
<CategoryCombobox {...field} />
|
||||||
disabled={isLoadingCategories}
|
|
||||||
options={(product_categories ?? []).map((category) => ({
|
|
||||||
label: category.name,
|
|
||||||
value: category.id,
|
|
||||||
}))}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="tags"
|
name="tag_ids"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
@@ -172,14 +184,15 @@ export const ProductOrganizationForm = ({
|
|||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Combobox
|
<Combobox
|
||||||
disabled={isLoadingTags}
|
|
||||||
options={(product_tags ?? []).map((tag) => ({
|
|
||||||
label: tag.value,
|
|
||||||
value: tag.id,
|
|
||||||
}))}
|
|
||||||
{...field}
|
{...field}
|
||||||
|
multiple
|
||||||
|
options={tags.options}
|
||||||
|
searchValue={tags.searchValue}
|
||||||
|
onSearchValueChange={tags.onSearchValueChange}
|
||||||
|
fetchNextPage={tags.fetchNextPage}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -193,7 +206,7 @@ export const ProductOrganizationForm = ({
|
|||||||
{t("actions.cancel")}
|
{t("actions.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</RouteDrawer.Close>
|
</RouteDrawer.Close>
|
||||||
<Button size="small" type="submit" isLoading={isLoading}>
|
<Button size="small" type="submit" isLoading={isPending}>
|
||||||
{t("actions.save")}
|
{t("actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+5
-2
@@ -4,13 +4,16 @@ import { useParams } from "react-router-dom"
|
|||||||
|
|
||||||
import { RouteDrawer } from "../../../components/route-modal"
|
import { RouteDrawer } from "../../../components/route-modal"
|
||||||
import { useProduct } from "../../../hooks/api/products"
|
import { useProduct } from "../../../hooks/api/products"
|
||||||
|
import { PRODUCT_DETAIL_FIELDS } from "../product-detail/constants"
|
||||||
import { ProductOrganizationForm } from "./components/product-organization-form"
|
import { ProductOrganizationForm } from "./components/product-organization-form"
|
||||||
|
|
||||||
export const ProductOrganization = () => {
|
export const ProductOrganization = () => {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { product, isLoading, isError, error } = useProduct(id!)
|
const { product, isLoading, isError, error } = useProduct(id!, {
|
||||||
|
fields: PRODUCT_DETAIL_FIELDS,
|
||||||
|
})
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
throw error
|
throw error
|
||||||
@@ -19,7 +22,7 @@ export const ProductOrganization = () => {
|
|||||||
return (
|
return (
|
||||||
<RouteDrawer>
|
<RouteDrawer>
|
||||||
<RouteDrawer.Header>
|
<RouteDrawer.Header>
|
||||||
<Heading>{t("products.editOrganization")}</Heading>
|
<Heading>{t("products.organization.edit.header")}</Heading>
|
||||||
</RouteDrawer.Header>
|
</RouteDrawer.Header>
|
||||||
{!isLoading && product && <ProductOrganizationForm product={product} />}
|
{!isLoading && product && <ProductOrganizationForm product={product} />}
|
||||||
</RouteDrawer>
|
</RouteDrawer>
|
||||||
|
|||||||
Reference in New Issue
Block a user