feat(dashboard,medusa,types): Add Product Tag management (#8349)
Resolves CC-69
This commit is contained in:
@@ -62,6 +62,13 @@ const PRODUCT_TYPE_INJECTION_ZONES = [
|
|||||||
"product_type.list.after",
|
"product_type.list.after",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
const PRODUCT_TAG_INJECTION_ZONES = [
|
||||||
|
"product_tag.details.before",
|
||||||
|
"product_tag.details.after",
|
||||||
|
"product_tag.list.before",
|
||||||
|
"product_tag.list.after",
|
||||||
|
] as const
|
||||||
|
|
||||||
const PRICE_LIST_INJECTION_ZONES = [
|
const PRICE_LIST_INJECTION_ZONES = [
|
||||||
"price_list.details.before",
|
"price_list.details.before",
|
||||||
"price_list.details.after",
|
"price_list.details.after",
|
||||||
@@ -206,4 +213,5 @@ export const INJECTION_ZONES = [
|
|||||||
...CAMPAIGN_INJECTION_ZONES,
|
...CAMPAIGN_INJECTION_ZONES,
|
||||||
...TAX_INJECTION_ZONES,
|
...TAX_INJECTION_ZONES,
|
||||||
...PRODUCT_TYPE_INJECTION_ZONES,
|
...PRODUCT_TYPE_INJECTION_ZONES,
|
||||||
|
...PRODUCT_TAG_INJECTION_ZONES,
|
||||||
] as const
|
] as const
|
||||||
|
|||||||
+4
@@ -50,6 +50,10 @@ const useSettingRoutes = (): NavItemProps[] => {
|
|||||||
label: t("productTypes.domain"),
|
label: t("productTypes.domain"),
|
||||||
to: "/settings/product-types",
|
to: "/settings/product-types",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t("productTags.domain"),
|
||||||
|
to: "/settings/product-tags",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t("stockLocations.domain"),
|
label: t("stockLocations.domain"),
|
||||||
to: "/settings/locations",
|
to: "/settings/locations",
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
import { FetchError } from "@medusajs/js-sdk"
|
import { FetchError } from "@medusajs/js-sdk"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
|
import {
|
||||||
|
QueryKey,
|
||||||
|
UseMutationOptions,
|
||||||
|
UseQueryOptions,
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
} from "@tanstack/react-query"
|
||||||
import { sdk } from "../../lib/client"
|
import { sdk } from "../../lib/client"
|
||||||
|
import { queryClient } from "../../lib/query-client"
|
||||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||||
|
|
||||||
const TAGS_QUERY_KEY = "tags" as const
|
const TAGS_QUERY_KEY = "tags" as const
|
||||||
export const tagsQueryKeys = queryKeysFactory(TAGS_QUERY_KEY)
|
export const productTagsQueryKeys = queryKeysFactory(TAGS_QUERY_KEY)
|
||||||
|
|
||||||
export const useTag = (
|
export const useProductTag = (
|
||||||
id: string,
|
id: string,
|
||||||
|
query?: HttpTypes.AdminProductTagParams,
|
||||||
options?: Omit<
|
options?: Omit<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
HttpTypes.AdminProductTagResponse,
|
HttpTypes.AdminProductTagResponse,
|
||||||
@@ -20,7 +28,7 @@ export const useTag = (
|
|||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
const { data, ...rest } = useQuery({
|
const { data, ...rest } = useQuery({
|
||||||
queryKey: tagsQueryKeys.detail(id),
|
queryKey: productTagsQueryKeys.detail(id, query),
|
||||||
queryFn: async () => sdk.admin.productTag.retrieve(id),
|
queryFn: async () => sdk.admin.productTag.retrieve(id),
|
||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
@@ -28,7 +36,7 @@ export const useTag = (
|
|||||||
return { ...data, ...rest }
|
return { ...data, ...rest }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTags = (
|
export const useProductTags = (
|
||||||
query?: HttpTypes.AdminProductTagListParams,
|
query?: HttpTypes.AdminProductTagListParams,
|
||||||
options?: Omit<
|
options?: Omit<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
@@ -41,10 +49,80 @@ export const useTags = (
|
|||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
const { data, ...rest } = useQuery({
|
const { data, ...rest } = useQuery({
|
||||||
queryKey: tagsQueryKeys.list(query),
|
queryKey: productTagsQueryKeys.list(query),
|
||||||
queryFn: async () => sdk.admin.productTag.list(query),
|
queryFn: async () => sdk.admin.productTag.list(query),
|
||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
|
|
||||||
return { ...data, ...rest }
|
return { ...data, ...rest }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useCreateProductTag = (
|
||||||
|
query?: HttpTypes.AdminProductTagParams,
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
HttpTypes.AdminProductTagResponse,
|
||||||
|
FetchError,
|
||||||
|
HttpTypes.AdminCreateProductTag
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (data) => sdk.admin.productTag.create(data, query),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: productTagsQueryKeys.lists(),
|
||||||
|
})
|
||||||
|
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateProductTag = (
|
||||||
|
id: string,
|
||||||
|
query?: HttpTypes.AdminProductTagParams,
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
HttpTypes.AdminProductTagResponse,
|
||||||
|
FetchError,
|
||||||
|
HttpTypes.AdminUpdateProductTag
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (data) => sdk.admin.productTag.update(id, data, query),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: productTagsQueryKeys.lists(),
|
||||||
|
})
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: productTagsQueryKeys.detail(data.product_tag.id, query),
|
||||||
|
})
|
||||||
|
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDeleteProductTag = (
|
||||||
|
id: string,
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
HttpTypes.AdminProductTagDeleteResponse,
|
||||||
|
FetchError,
|
||||||
|
void
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async () => sdk.admin.productTag.delete(id),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: productTagsQueryKeys.lists(),
|
||||||
|
})
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: productTagsQueryKeys.detail(id),
|
||||||
|
})
|
||||||
|
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
+40
-16
@@ -1,5 +1,6 @@
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Filter } from "../../../components/table/data-table"
|
import { Filter } from "../../../components/table/data-table"
|
||||||
|
import { useProductTags } from "../../api"
|
||||||
import { useProductTypes } from "../../api/product-types"
|
import { useProductTypes } from "../../api/product-types"
|
||||||
import { useSalesChannels } from "../../api/sales-channels"
|
import { useSalesChannels } from "../../api/sales-channels"
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ const excludeableFields = [
|
|||||||
"collections",
|
"collections",
|
||||||
"categories",
|
"categories",
|
||||||
"product_types",
|
"product_types",
|
||||||
|
"product_tags",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const useProductTableFilters = (
|
export const useProductTableFilters = (
|
||||||
@@ -27,6 +29,13 @@ export const useProductTableFilters = (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isProductTagExcluded = exclude?.includes("product_tags")
|
||||||
|
|
||||||
|
const { product_tags } = useProductTags({
|
||||||
|
limit: 1000,
|
||||||
|
offset: 0,
|
||||||
|
})
|
||||||
|
|
||||||
// const { product_tags } = useAdminProductTags({
|
// const { product_tags } = useAdminProductTags({
|
||||||
// limit: 1000,
|
// limit: 1000,
|
||||||
// offset: 0,
|
// offset: 0,
|
||||||
@@ -84,6 +93,21 @@ export const useProductTableFilters = (
|
|||||||
filters = [...filters, typeFilter]
|
filters = [...filters, typeFilter]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (product_tags && !isProductTagExcluded) {
|
||||||
|
const tagFilter: Filter = {
|
||||||
|
key: "tags",
|
||||||
|
label: t("fields.tag"),
|
||||||
|
type: "select",
|
||||||
|
multiple: true,
|
||||||
|
options: product_tags.map((t) => ({
|
||||||
|
label: t.value,
|
||||||
|
value: t.id,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
filters = [...filters, tagFilter]
|
||||||
|
}
|
||||||
|
|
||||||
// if (product_tags) {
|
// if (product_tags) {
|
||||||
// const tagFilter: Filter = {
|
// const tagFilter: Filter = {
|
||||||
// key: "tags",
|
// key: "tags",
|
||||||
@@ -144,21 +168,21 @@ export const useProductTableFilters = (
|
|||||||
// filters = [...filters, collectionFilter]
|
// filters = [...filters, collectionFilter]
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const giftCardFilter: Filter = {
|
// const giftCardFilter: Filter = {
|
||||||
key: "is_giftcard",
|
// key: "is_giftcard",
|
||||||
label: t("fields.giftCard"),
|
// label: t("fields.giftCard"),
|
||||||
type: "select",
|
// type: "select",
|
||||||
options: [
|
// options: [
|
||||||
{
|
// {
|
||||||
label: t("fields.true"),
|
// label: t("fields.true"),
|
||||||
value: "true",
|
// value: "true",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
label: t("fields.false"),
|
// label: t("fields.false"),
|
||||||
value: "false",
|
// value: "false",
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
}
|
// }
|
||||||
|
|
||||||
const statusFilter: Filter = {
|
const statusFilter: Filter = {
|
||||||
key: "status",
|
key: "status",
|
||||||
@@ -194,7 +218,7 @@ export const useProductTableFilters = (
|
|||||||
type: "date",
|
type: "date",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
filters = [...filters, statusFilter, giftCardFilter, ...dateFilters]
|
filters = [...filters, statusFilter, ...dateFilters]
|
||||||
|
|
||||||
return filters
|
return filters
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2172,6 +2172,26 @@
|
|||||||
"value": "Value"
|
"value": "Value"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"productTags": {
|
||||||
|
"domain": "Product Tags",
|
||||||
|
"create": {
|
||||||
|
"header": "Create Product Tag",
|
||||||
|
"subtitle": "Create a new product tag to categorize your products.",
|
||||||
|
"successToast": "Product tag {{value}} was successfully created."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit Product Tag",
|
||||||
|
"subtitle": "Edit the value of the product tag.",
|
||||||
|
"successToast": "Product tag {{value}} was successfully updated."
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"confirmation": "You are about to delete the product tag {{value}}. This action cannot be undone.",
|
||||||
|
"successToast": "Product tag {{value}} was successfully deleted."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"value": "Value"
|
||||||
|
}
|
||||||
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"domain": "Notifications",
|
"domain": "Notifications",
|
||||||
"emptyState": {
|
"emptyState": {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AdminApiKeyResponse,
|
|
||||||
AdminProductCategoryResponse,
|
AdminProductCategoryResponse,
|
||||||
AdminTaxRegionResponse,
|
AdminTaxRegionResponse,
|
||||||
HttpTypes,
|
HttpTypes,
|
||||||
SalesChannelDTO,
|
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { Outlet, RouteObject } from "react-router-dom"
|
import { Outlet, RouteObject } from "react-router-dom"
|
||||||
|
|
||||||
@@ -739,7 +737,7 @@ export const RouteMap: RouteObject[] = [
|
|||||||
path: ":id",
|
path: ":id",
|
||||||
lazy: () => import("../../routes/users/user-detail"),
|
lazy: () => import("../../routes/users/user-detail"),
|
||||||
handle: {
|
handle: {
|
||||||
crumb: (data: { user: UserDTO }) => data.user.email,
|
crumb: (data: HttpTypes.AdminUserResponse) => data.user.email,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -776,7 +774,7 @@ export const RouteMap: RouteObject[] = [
|
|||||||
lazy: () =>
|
lazy: () =>
|
||||||
import("../../routes/sales-channels/sales-channel-detail"),
|
import("../../routes/sales-channels/sales-channel-detail"),
|
||||||
handle: {
|
handle: {
|
||||||
crumb: (data: { sales_channel: SalesChannelDTO }) =>
|
crumb: (data: HttpTypes.AdminSalesChannelResponse) =>
|
||||||
data.sales_channel.name,
|
data.sales_channel.name,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
@@ -944,7 +942,43 @@ export const RouteMap: RouteObject[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "product-tags",
|
||||||
|
element: <Outlet />,
|
||||||
|
handle: {
|
||||||
|
crumb: () => "Product Tags",
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
lazy: () =>
|
||||||
|
import("../../routes/product-tags/product-tag-list"),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "create",
|
||||||
|
lazy: () =>
|
||||||
|
import("../../routes/product-tags/product-tag-create"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ":id",
|
||||||
|
lazy: () =>
|
||||||
|
import("../../routes/product-tags/product-tag-detail"),
|
||||||
|
handle: {
|
||||||
|
crumb: (data: HttpTypes.AdminProductTagResponse) =>
|
||||||
|
data.product_tag.value,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "edit",
|
||||||
|
lazy: () =>
|
||||||
|
import("../../routes/product-tags/product-tag-edit"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "workflows",
|
path: "workflows",
|
||||||
element: <Outlet />,
|
element: <Outlet />,
|
||||||
@@ -1051,7 +1085,7 @@ export const RouteMap: RouteObject[] = [
|
|||||||
"../../routes/api-key-management/api-key-management-detail"
|
"../../routes/api-key-management/api-key-management-detail"
|
||||||
),
|
),
|
||||||
handle: {
|
handle: {
|
||||||
crumb: (data: AdminApiKeyResponse) => {
|
crumb: (data: HttpTypes.AdminApiKeyResponse) => {
|
||||||
return data.api_key.title
|
return data.api_key.title
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1110,7 +1144,7 @@ export const RouteMap: RouteObject[] = [
|
|||||||
"../../routes/api-key-management/api-key-management-detail"
|
"../../routes/api-key-management/api-key-management-detail"
|
||||||
),
|
),
|
||||||
handle: {
|
handle: {
|
||||||
crumb: (data: AdminApiKeyResponse) => {
|
crumb: (data: HttpTypes.AdminApiKeyResponse) => {
|
||||||
return data.api_key.title
|
return data.api_key.title
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { toast, usePrompt } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useDeleteProductTag } from "../../../../hooks/api"
|
||||||
|
|
||||||
|
type UseDeleteProductTagActionProps = {
|
||||||
|
productTag: HttpTypes.AdminProductTag
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDeleteProductTagAction = ({
|
||||||
|
productTag,
|
||||||
|
}: UseDeleteProductTagActionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const prompt = usePrompt()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const { mutateAsync } = useDeleteProductTag(productTag.id)
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
const confirmed = await prompt({
|
||||||
|
title: t("general.areYouSure"),
|
||||||
|
description: t("productTags.delete.confirmation", {
|
||||||
|
value: productTag.value,
|
||||||
|
}),
|
||||||
|
confirmText: t("actions.delete"),
|
||||||
|
cancelText: t("actions.cancel"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await mutateAsync(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(
|
||||||
|
t("productTags.delete.successToast", {
|
||||||
|
value: productTag.value,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
navigate("/settings/product-tags", {
|
||||||
|
replace: true,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleDelete
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./product-tag-create-form"
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { Button, Heading, Input, Text, toast } from "@medusajs/ui"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { Form } from "../../../../../components/common/form"
|
||||||
|
import {
|
||||||
|
RouteFocusModal,
|
||||||
|
useRouteModal,
|
||||||
|
} from "../../../../../components/modals"
|
||||||
|
import { useCreateProductTag } from "../../../../../hooks/api"
|
||||||
|
|
||||||
|
const ProductTagCreateSchema = z.object({
|
||||||
|
value: z.string().min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ProductTagCreateForm = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof ProductTagCreateSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(ProductTagCreateSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useCreateProductTag()
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
await mutateAsync(data, {
|
||||||
|
onSuccess: ({ product_tag }) => {
|
||||||
|
toast.success(
|
||||||
|
t("productTags.create.successToast", {
|
||||||
|
value: product_tag.value,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
handleSuccess(`../${product_tag.id}`)
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteFocusModal.Form form={form}>
|
||||||
|
<form
|
||||||
|
className="flex size-full flex-col overflow-hidden"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<RouteFocusModal.Header />
|
||||||
|
<RouteFocusModal.Body className="flex flex-1 justify-center overflow-auto px-6 py-16">
|
||||||
|
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
|
||||||
|
<div className="flex flex-col gap-y-1">
|
||||||
|
<RouteFocusModal.Title asChild>
|
||||||
|
<Heading>{t("productTags.create.header")}</Heading>
|
||||||
|
</RouteFocusModal.Title>
|
||||||
|
<RouteFocusModal.Description asChild>
|
||||||
|
<Text size="small" className="text-ui-fg-subtle">
|
||||||
|
{t("productTags.create.subtitle")}
|
||||||
|
</Text>
|
||||||
|
</RouteFocusModal.Description>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="value"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("productTags.fields.value")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RouteFocusModal.Body>
|
||||||
|
<RouteFocusModal.Footer>
|
||||||
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
<RouteFocusModal.Close asChild>
|
||||||
|
<Button size="small" variant="secondary" type="button">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteFocusModal.Close>
|
||||||
|
<Button size="small" type="submit" isLoading={isPending}>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteFocusModal.Footer>
|
||||||
|
</form>
|
||||||
|
</RouteFocusModal.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ProductTagCreate as Component } from "./product-tag-create"
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import { RouteFocusModal } from "../../../components/modals"
|
||||||
|
import { ProductTagCreateForm } from "./components/product-tag-create-form"
|
||||||
|
|
||||||
|
export const ProductTagCreate = () => {
|
||||||
|
return (
|
||||||
|
<RouteFocusModal>
|
||||||
|
<ProductTagCreateForm />
|
||||||
|
</RouteFocusModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./product-tag-general-section"
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Container, Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
|
import { useDeleteProductTagAction } from "../../../common/hooks/use-delete-product-tag-action"
|
||||||
|
|
||||||
|
type ProductTagGeneralSectionProps = {
|
||||||
|
productTag: HttpTypes.AdminProductTag
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProductTagGeneralSection = ({
|
||||||
|
productTag,
|
||||||
|
}: ProductTagGeneralSectionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const handleDelete = useDeleteProductTagAction({ productTag })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-x-1.5">
|
||||||
|
<span className="text-ui-fg-muted h1-core">#</span>
|
||||||
|
<Heading>{productTag.value}</Heading>
|
||||||
|
</div>
|
||||||
|
<ActionMenu
|
||||||
|
groups={[
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
label: t("actions.edit"),
|
||||||
|
to: "edit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
icon: <Trash />,
|
||||||
|
label: t("actions.delete"),
|
||||||
|
onClick: handleDelete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./product-tag-product-section"
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Container, Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { DataTable } from "../../../../../components/table/data-table"
|
||||||
|
import { useProducts } from "../../../../../hooks/api"
|
||||||
|
import { useProductTableColumns } from "../../../../../hooks/table/columns"
|
||||||
|
import { useProductTableFilters } from "../../../../../hooks/table/filters"
|
||||||
|
import { useProductTableQuery } from "../../../../../hooks/table/query"
|
||||||
|
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||||
|
|
||||||
|
type ProductTagProductSectionProps = {
|
||||||
|
productTag: HttpTypes.AdminProductTag
|
||||||
|
}
|
||||||
|
|
||||||
|
const PAGE_SIZE = 10
|
||||||
|
const PREFIX = "pt"
|
||||||
|
|
||||||
|
export const ProductTagProductSection = ({
|
||||||
|
productTag,
|
||||||
|
}: ProductTagProductSectionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { searchParams, raw } = useProductTableQuery({
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
prefix: PREFIX,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { products, count, isPending, isError, error } = useProducts({
|
||||||
|
...searchParams,
|
||||||
|
tags: productTag.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
const filters = useProductTableFilters(["product_tags"])
|
||||||
|
const columns = useProductTableColumns()
|
||||||
|
|
||||||
|
const { table } = useDataTable({
|
||||||
|
data: products,
|
||||||
|
count,
|
||||||
|
columns,
|
||||||
|
getRowId: (row) => row.id,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
prefix: PREFIX,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="divide-y px-0 py-0">
|
||||||
|
<div className="px-6 py-4">
|
||||||
|
<Heading level="h2">{t("products.domain")}</Heading>
|
||||||
|
</div>
|
||||||
|
<DataTable
|
||||||
|
table={table}
|
||||||
|
filters={filters}
|
||||||
|
queryObject={raw}
|
||||||
|
isLoading={isPending}
|
||||||
|
columns={columns}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
count={count}
|
||||||
|
navigateTo={(row) => row.original.id}
|
||||||
|
search
|
||||||
|
pagination
|
||||||
|
orderBy={["title", "status", "created_at", "updated_at"]}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { productTagLoader as loader } from "./loader"
|
||||||
|
export { ProductTagDetail as Component } from "./product-tag-detail"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { LoaderFunctionArgs } from "react-router-dom"
|
||||||
|
|
||||||
|
import { productTagsQueryKeys } from "../../../hooks/api"
|
||||||
|
import { sdk } from "../../../lib/client"
|
||||||
|
import { queryClient } from "../../../lib/query-client"
|
||||||
|
|
||||||
|
const productTagDetailQuery = (id: string) => ({
|
||||||
|
queryKey: productTagsQueryKeys.detail(id),
|
||||||
|
queryFn: async () => sdk.admin.productTag.retrieve(id),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const productTagLoader = async ({ params }: LoaderFunctionArgs) => {
|
||||||
|
const id = params.id
|
||||||
|
const query = productTagDetailQuery(id!)
|
||||||
|
|
||||||
|
return (
|
||||||
|
queryClient.getQueryData<any>(query.queryKey) ??
|
||||||
|
(await queryClient.fetchQuery(query))
|
||||||
|
)
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
import { useLoaderData, useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
|
||||||
|
import { SingleColumnPage } from "../../../components/layout/pages"
|
||||||
|
import { useProductTag } from "../../../hooks/api"
|
||||||
|
import { ProductTagGeneralSection } from "./components/product-tag-general-section"
|
||||||
|
import { ProductTagProductSection } from "./components/product-tag-product-section"
|
||||||
|
import { productTagLoader } from "./loader"
|
||||||
|
|
||||||
|
import after from "virtual:medusa/widgets/product_tag/details/after"
|
||||||
|
import before from "virtual:medusa/widgets/product_tag/details/before"
|
||||||
|
|
||||||
|
export const ProductTagDetail = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
|
||||||
|
const initialData = useLoaderData() as Awaited<
|
||||||
|
ReturnType<typeof productTagLoader>
|
||||||
|
>
|
||||||
|
|
||||||
|
const { product_tag, isPending, isError, error } = useProductTag(
|
||||||
|
id!,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
initialData,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isPending || !product_tag) {
|
||||||
|
return <SingleColumnPageSkeleton showJSON sections={2} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SingleColumnPage widgets={{ after, before }} showJSON data={product_tag}>
|
||||||
|
<ProductTagGeneralSection productTag={product_tag} />
|
||||||
|
<ProductTagProductSection productTag={product_tag} />
|
||||||
|
</SingleColumnPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./product-tag-edit-form"
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, Input, toast } from "@medusajs/ui"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { Form } from "../../../../../components/common/form"
|
||||||
|
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||||
|
import { useUpdateProductTag } from "../../../../../hooks/api"
|
||||||
|
|
||||||
|
type ProductTagEditFormProps = {
|
||||||
|
productTag: HttpTypes.AdminProductTag
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductTagEditSchema = z.object({
|
||||||
|
value: z.string().min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ProductTagEditForm = ({ productTag }: ProductTagEditFormProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof ProductTagEditSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
value: productTag.value,
|
||||||
|
},
|
||||||
|
resolver: zodResolver(ProductTagEditSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useUpdateProductTag(productTag.id)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
await mutateAsync(data, {
|
||||||
|
onSuccess: ({ product_tag }) => {
|
||||||
|
toast.success(
|
||||||
|
t("productTags.edit.successToast", {
|
||||||
|
value: product_tag.value,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
handleSuccess()
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer.Form form={form}>
|
||||||
|
<form
|
||||||
|
className="flex size-full flex-col overflow-hidden"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<RouteDrawer.Body className="flex flex-1 flex-col overflow-auto">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="value"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("productTags.fields.value")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RouteDrawer.Body>
|
||||||
|
<RouteDrawer.Footer>
|
||||||
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
|
<RouteDrawer.Close asChild>
|
||||||
|
<Button variant="secondary" size="small" type="button">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteDrawer.Close>
|
||||||
|
<Button size="small" type="submit" isLoading={isPending}>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Footer>
|
||||||
|
</form>
|
||||||
|
</RouteDrawer.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ProductTagEdit as Component } from "./product-tag-edit"
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import { Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
import { RouteDrawer } from "../../../components/modals"
|
||||||
|
import { useProductTag } from "../../../hooks/api"
|
||||||
|
import { ProductTagEditForm } from "./components/product-tag-edit-form"
|
||||||
|
|
||||||
|
export const ProductTagEdit = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { product_tag, isPending, isError, error } = useProductTag(id!)
|
||||||
|
|
||||||
|
const ready = !isPending && !!product_tag
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer>
|
||||||
|
<RouteDrawer.Header>
|
||||||
|
<RouteDrawer.Title asChild>
|
||||||
|
<Heading>{t("productTags.edit.header")}</Heading>
|
||||||
|
</RouteDrawer.Title>
|
||||||
|
<RouteDrawer.Description className="sr-only">
|
||||||
|
{t("productTags.edit.subtitle")}
|
||||||
|
</RouteDrawer.Description>
|
||||||
|
</RouteDrawer.Header>
|
||||||
|
{ready && <ProductTagEditForm productTag={product_tag} />}
|
||||||
|
</RouteDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./product-tag-list-table"
|
||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, Container, Heading } from "@medusajs/ui"
|
||||||
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { Link, useLoaderData } from "react-router-dom"
|
||||||
|
|
||||||
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
|
import { DataTable } from "../../../../../components/table/data-table"
|
||||||
|
import { useProductTags } from "../../../../../hooks/api"
|
||||||
|
import { useProductTagTableColumns } from "../../../../../hooks/table/columns"
|
||||||
|
import { useProductTagTableFilters } from "../../../../../hooks/table/filters"
|
||||||
|
import { useProductTagTableQuery } from "../../../../../hooks/table/query"
|
||||||
|
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||||
|
import { useDeleteProductTagAction } from "../../../common/hooks/use-delete-product-tag-action"
|
||||||
|
import { productTagListLoader } from "../../loader"
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
|
export const ProductTagListTable = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { searchParams, raw } = useProductTagTableQuery({
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
})
|
||||||
|
|
||||||
|
const initialData = useLoaderData() as Awaited<
|
||||||
|
ReturnType<typeof productTagListLoader>
|
||||||
|
>
|
||||||
|
|
||||||
|
const { product_tags, count, isPending, isError, error } = useProductTags(
|
||||||
|
searchParams,
|
||||||
|
{
|
||||||
|
initialData,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const columns = useColumns()
|
||||||
|
const filters = useProductTagTableFilters()
|
||||||
|
|
||||||
|
const { table } = useDataTable({
|
||||||
|
data: product_tags,
|
||||||
|
count,
|
||||||
|
columns,
|
||||||
|
getRowId: (row) => row.id,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="divide-y px-0 py-0">
|
||||||
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
|
<Heading>{t("productTags.domain")}</Heading>
|
||||||
|
<Button variant="secondary" size="small" asChild>
|
||||||
|
<Link to="create">{t("actions.create")}</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DataTable
|
||||||
|
table={table}
|
||||||
|
filters={filters}
|
||||||
|
queryObject={raw}
|
||||||
|
isLoading={isPending}
|
||||||
|
columns={columns}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
count={count}
|
||||||
|
navigateTo={(row) => row.original.id}
|
||||||
|
search
|
||||||
|
pagination
|
||||||
|
orderBy={["value", "created_at", "updated_at"]}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductTagRowActions = ({
|
||||||
|
productTag,
|
||||||
|
}: {
|
||||||
|
productTag: HttpTypes.AdminProductTag
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const handleDelete = useDeleteProductTagAction({ productTag })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionMenu
|
||||||
|
groups={[
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
label: t("actions.edit"),
|
||||||
|
to: `${productTag.id}/edit`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
icon: <Trash />,
|
||||||
|
label: t("actions.delete"),
|
||||||
|
onClick: handleDelete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<HttpTypes.AdminProductTag>()
|
||||||
|
|
||||||
|
const useColumns = () => {
|
||||||
|
const base = useProductTagTableColumns()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
...base,
|
||||||
|
columnHelper.display({
|
||||||
|
id: "actions",
|
||||||
|
cell: ({ row }) => <ProductTagRowActions productTag={row.original} />,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[base]
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { productTagListLoader as loader } from "./loader"
|
||||||
|
export { ProductTagList as Component } from "./product-tag-list"
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { LoaderFunctionArgs } from "react-router-dom"
|
||||||
|
|
||||||
|
import { productTagsQueryKeys } from "../../../hooks/api"
|
||||||
|
import { sdk } from "../../../lib/client"
|
||||||
|
import { queryClient } from "../../../lib/query-client"
|
||||||
|
|
||||||
|
const productTagListQuery = (query?: HttpTypes.AdminProductTagListParams) => ({
|
||||||
|
queryKey: productTagsQueryKeys.list(query),
|
||||||
|
queryFn: async () => sdk.admin.productTag.list(query),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const productTagListLoader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
|
const searchParams = new URL(request.url).searchParams
|
||||||
|
|
||||||
|
const queryObject: Record<string, string> = {}
|
||||||
|
|
||||||
|
searchParams.forEach((value, key) => {
|
||||||
|
try {
|
||||||
|
queryObject[key] = JSON.parse(value)
|
||||||
|
} catch (_e) {
|
||||||
|
queryObject[key] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const query = productTagListQuery(
|
||||||
|
queryObject as HttpTypes.AdminProductTagListParams
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
queryClient.getQueryData<any>(query.queryKey) ??
|
||||||
|
(await queryClient.fetchQuery(query))
|
||||||
|
)
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import { SingleColumnPage } from "../../../components/layout/pages"
|
||||||
|
import { ProductTagListTable } from "./components/product-tag-list-table"
|
||||||
|
|
||||||
|
import after from "virtual:medusa/widgets/product_tag/list/after"
|
||||||
|
import before from "virtual:medusa/widgets/product_tag/list/before"
|
||||||
|
|
||||||
|
export const ProductTagList = () => {
|
||||||
|
return (
|
||||||
|
<SingleColumnPage
|
||||||
|
showMetadata={false}
|
||||||
|
showJSON={false}
|
||||||
|
hasOutlet
|
||||||
|
widgets={{
|
||||||
|
after,
|
||||||
|
before,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProductTagListTable />
|
||||||
|
</SingleColumnPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
+26
-26
@@ -2,46 +2,46 @@ import { HttpTypes } from "@medusajs/types"
|
|||||||
import { Button, Checkbox } from "@medusajs/ui"
|
import { Button, Checkbox } from "@medusajs/ui"
|
||||||
import { keepPreviousData } from "@tanstack/react-query"
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
import {
|
import {
|
||||||
OnChangeFn,
|
OnChangeFn,
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
createColumnHelper,
|
createColumnHelper,
|
||||||
} from "@tanstack/react-table"
|
} from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
import { useSearchParams } from "react-router-dom"
|
import { useSearchParams } from "react-router-dom"
|
||||||
import {
|
import {
|
||||||
StackedDrawer,
|
StackedDrawer,
|
||||||
StackedFocusModal,
|
StackedFocusModal,
|
||||||
} from "../../../../../components/modals"
|
} from "../../../../../components/modals"
|
||||||
import { DataTable } from "../../../../../components/table/data-table"
|
import { DataTable } from "../../../../../components/table/data-table"
|
||||||
import {
|
import {
|
||||||
useCollections,
|
useCollections,
|
||||||
useCustomerGroups,
|
useCustomerGroups,
|
||||||
useProductTypes,
|
useProductTags,
|
||||||
useProducts,
|
useProductTypes,
|
||||||
useTags,
|
useProducts,
|
||||||
} from "../../../../../hooks/api"
|
} from "../../../../../hooks/api"
|
||||||
import {
|
import {
|
||||||
useCollectionTableColumns,
|
useCollectionTableColumns,
|
||||||
useCustomerGroupTableColumns,
|
useCustomerGroupTableColumns,
|
||||||
useProductTableColumns,
|
useProductTableColumns,
|
||||||
useProductTagTableColumns,
|
useProductTagTableColumns,
|
||||||
useProductTypeTableColumns,
|
useProductTypeTableColumns,
|
||||||
} from "../../../../../hooks/table/columns"
|
} from "../../../../../hooks/table/columns"
|
||||||
import {
|
import {
|
||||||
useCollectionTableFilters,
|
useCollectionTableFilters,
|
||||||
useCustomerGroupTableFilters,
|
useCustomerGroupTableFilters,
|
||||||
useProductTableFilters,
|
useProductTableFilters,
|
||||||
useProductTagTableFilters,
|
useProductTagTableFilters,
|
||||||
useProductTypeTableFilters,
|
useProductTypeTableFilters,
|
||||||
} from "../../../../../hooks/table/filters"
|
} from "../../../../../hooks/table/filters"
|
||||||
import {
|
import {
|
||||||
useCollectionTableQuery,
|
useCollectionTableQuery,
|
||||||
useCustomerGroupTableQuery,
|
useCustomerGroupTableQuery,
|
||||||
useProductTableQuery,
|
useProductTableQuery,
|
||||||
useProductTagTableQuery,
|
useProductTagTableQuery,
|
||||||
useProductTypeTableQuery,
|
useProductTypeTableQuery,
|
||||||
} from "../../../../../hooks/table/query"
|
} from "../../../../../hooks/table/query"
|
||||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||||
import { TaxRateRuleReferenceType } from "../../constants"
|
import { TaxRateRuleReferenceType } from "../../constants"
|
||||||
@@ -664,7 +664,7 @@ const ProductTagTable = ({
|
|||||||
prefix: PREFIX_PRODUCT_TAG,
|
prefix: PREFIX_PRODUCT_TAG,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { product_tags, count, isLoading, isError, error } = useTags(
|
const { product_tags, count, isLoading, isError, error } = useProductTags(
|
||||||
searchParams,
|
searchParams,
|
||||||
{
|
{
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
|
|||||||
+6
-6
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
ArrowDownRightMini,
|
ArrowDownRightMini,
|
||||||
PencilSquare,
|
PencilSquare,
|
||||||
Trash,
|
Trash,
|
||||||
TriangleRightMini,
|
TriangleRightMini,
|
||||||
} from "@medusajs/icons"
|
} from "@medusajs/icons"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { Badge, IconButton, StatusBadge, Text, Tooltip } from "@medusajs/ui"
|
import { Badge, IconButton, StatusBadge, Text, Tooltip } from "@medusajs/ui"
|
||||||
@@ -17,7 +17,7 @@ import { useCollections } from "../../../../../hooks/api/collections"
|
|||||||
import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
|
import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
|
||||||
import { useProductTypes } from "../../../../../hooks/api/product-types"
|
import { useProductTypes } from "../../../../../hooks/api/product-types"
|
||||||
import { useProducts } from "../../../../../hooks/api/products"
|
import { useProducts } from "../../../../../hooks/api/products"
|
||||||
import { useTags } from "../../../../../hooks/api/tags"
|
import { useProductTags } from "../../../../../hooks/api/tags"
|
||||||
import { formatPercentage } from "../../../../../lib/percentage-helpers"
|
import { formatPercentage } from "../../../../../lib/percentage-helpers"
|
||||||
import { TaxRateRuleReferenceType } from "../../constants"
|
import { TaxRateRuleReferenceType } from "../../constants"
|
||||||
import { useDeleteTaxRateAction } from "../../hooks"
|
import { useDeleteTaxRateAction } from "../../hooks"
|
||||||
@@ -277,7 +277,7 @@ const useReferenceValues = (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const tags = useTags(
|
const tags = useProductTags(
|
||||||
{
|
{
|
||||||
id: ids,
|
id: ids,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ import { useCollections } from "../../../hooks/api/collections"
|
|||||||
import { useCustomerGroups } from "../../../hooks/api/customer-groups"
|
import { useCustomerGroups } from "../../../hooks/api/customer-groups"
|
||||||
import { useProductTypes } from "../../../hooks/api/product-types"
|
import { useProductTypes } from "../../../hooks/api/product-types"
|
||||||
import { useProducts } from "../../../hooks/api/products"
|
import { useProducts } from "../../../hooks/api/products"
|
||||||
import { useTags } from "../../../hooks/api/tags"
|
import { useProductTags } from "../../../hooks/api/tags"
|
||||||
import { useTaxRate } from "../../../hooks/api/tax-rates"
|
import { useTaxRate } from "../../../hooks/api/tax-rates"
|
||||||
import { TaxRateRuleReferenceType } from "../common/constants"
|
import { TaxRateRuleReferenceType } from "../common/constants"
|
||||||
import { TaxRegionTaxOverrideEditForm } from "./components/tax-region-tax-override-edit-form"
|
import { TaxRegionTaxOverrideEditForm } from "./components/tax-region-tax-override-edit-form"
|
||||||
@@ -93,7 +93,7 @@ const useDefaultRulesValues = (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
ids: idsByReferenceType[TaxRateRuleReferenceType.PRODUCT_TAG],
|
ids: idsByReferenceType[TaxRateRuleReferenceType.PRODUCT_TAG],
|
||||||
hook: useTags,
|
hook: useProductTags,
|
||||||
key: TaxRateRuleReferenceType.PRODUCT_TAG,
|
key: TaxRateRuleReferenceType.PRODUCT_TAG,
|
||||||
getResult: (result: any) =>
|
getResult: (result: any) =>
|
||||||
result.tags.map((tag: any) => ({
|
result.tags.map((tag: any) => ({
|
||||||
|
|||||||
@@ -111,13 +111,11 @@ export interface BaseProductListParams
|
|||||||
handle?: string | string[]
|
handle?: string | string[]
|
||||||
id?: string | string[]
|
id?: string | string[]
|
||||||
is_giftcard?: boolean
|
is_giftcard?: boolean
|
||||||
tags?: {
|
tags?: string | string[]
|
||||||
value?: string[]
|
|
||||||
}
|
|
||||||
type_id?: string | string[]
|
type_id?: string | string[]
|
||||||
category_id?: string | string[] | OperatorMap<string>
|
category_id?: string | string[]
|
||||||
categories?: { id: OperatorMap<string> } | { id: OperatorMap<string[]> }
|
categories?: string | string[]
|
||||||
collection_id?: string | string[] | OperatorMap<string>
|
collection_id?: string | string[]
|
||||||
created_at?: OperatorMap<string>
|
created_at?: OperatorMap<string>
|
||||||
updated_at?: OperatorMap<string>
|
updated_at?: OperatorMap<string>
|
||||||
deleted_at?: OperatorMap<string>
|
deleted_at?: OperatorMap<string>
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ export const GetProductsParams = z.object({
|
|||||||
title: z.string().nullish(),
|
title: z.string().nullish(),
|
||||||
handle: z.string().nullish(),
|
handle: z.string().nullish(),
|
||||||
is_giftcard: OptionalBooleanValidator,
|
is_giftcard: OptionalBooleanValidator,
|
||||||
category_id: z.string().array().nullish(),
|
category_id: z.union([z.string(), z.array(z.string())]).nullish(),
|
||||||
sales_channel_id: z.string().array().nullish(),
|
sales_channel_id: z.union([z.string(), z.array(z.string())]).nullish(),
|
||||||
collection_id: z.string().array().nullish(),
|
collection_id: z.union([z.string(), z.array(z.string())]).nullish(),
|
||||||
tags: z.string().array().optional(),
|
tags: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
type_id: z.string().array().optional(),
|
type_id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
created_at: createOperatorMap().optional(),
|
created_at: createOperatorMap().optional(),
|
||||||
updated_at: createOperatorMap().optional(),
|
updated_at: createOperatorMap().optional(),
|
||||||
deleted_at: createOperatorMap().optional(),
|
deleted_at: createOperatorMap().optional(),
|
||||||
|
|||||||
Reference in New Issue
Block a user