Feat: admin return reason list (#8482)

* feat: add missing endpoints to return reason sdk

* add return reason list route

* add return reason query hooks

* fix update return reasons in order modules service

* fix store/return-reasons middleware

* add missing tests for /return-resasons/:id
This commit is contained in:
Christian
2024-08-08 16:24:13 +02:00
committed by GitHub
parent f9989e1659
commit d4c5f6593d
36 changed files with 724 additions and 90 deletions
@@ -42,6 +42,10 @@ const useSettingRoutes = (): NavItemProps[] => {
label: t("taxRegions.domain"),
to: "/settings/tax-regions",
},
{
label: t("returnReasons.domain"),
to: "/settings/return-reasons",
},
{
label: t("salesChannels.domain"),
to: "/settings/sales-channels",
@@ -1,19 +1,27 @@
import { HttpTypes } from "@medusajs/types"
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
import {
QueryKey,
UseMutationOptions,
UseQueryOptions,
useMutation,
useQuery,
} from "@tanstack/react-query"
import { FetchError } from "@medusajs/js-sdk"
import { sdk } from "../../lib/client"
import { queryClient } from "../../lib/query-client"
import { queryKeysFactory } from "../../lib/query-key-factory"
const RETURN_REASONS_QUERY_KEY = "return_reasons" as const
export const returnReasonQueryKeys = queryKeysFactory(RETURN_REASONS_QUERY_KEY)
export const returnReasonsQueryKeys = queryKeysFactory(RETURN_REASONS_QUERY_KEY)
export const useReturnReasons = (
query?: HttpTypes.AdminReturnReasonListParams,
options?: Omit<
UseQueryOptions<
HttpTypes.AdminReturnReasonsResponse,
Error,
HttpTypes.AdminReturnReasonsResponse,
HttpTypes.AdminReturnReasonListResponse,
FetchError,
HttpTypes.AdminReturnReasonListResponse,
QueryKey
>,
"queryFn" | "queryKey"
@@ -21,9 +29,104 @@ export const useReturnReasons = (
) => {
const { data, ...rest } = useQuery({
queryFn: () => sdk.admin.returnReason.list(query),
queryKey: returnReasonQueryKeys.list(query),
queryKey: returnReasonsQueryKeys.list(query),
...options,
})
return { ...data, ...rest }
}
export const useReturnReason = (
id: string,
query?: HttpTypes.AdminReturnReasonParams,
options?: Omit<
UseQueryOptions<
HttpTypes.AdminReturnReasonResponse,
FetchError,
HttpTypes.AdminReturnReasonResponse,
QueryKey
>,
"queryFn" | "queryKey"
>
) => {
const { data, ...rest } = useQuery({
queryFn: () => sdk.admin.returnReason.retrieve(id, query),
queryKey: returnReasonsQueryKeys.detail(id),
...options,
})
return { ...data, ...rest }
}
export const useCreateReturnReason = (
query?: HttpTypes.AdminReturnReasonParams,
options?: UseMutationOptions<
HttpTypes.AdminReturnReasonResponse,
FetchError,
HttpTypes.AdminCreateReturnReason
>
) => {
return useMutation({
mutationFn: async (data) => sdk.admin.returnReason.create(data, query),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: returnReasonsQueryKeys.lists(),
})
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
export const useUpdateReturnReason = (
id: string,
query?: HttpTypes.AdminReturnReasonParams,
options?: UseMutationOptions<
HttpTypes.AdminReturnReasonResponse,
FetchError,
HttpTypes.AdminUpdateReturnReason
>
) => {
return useMutation({
mutationFn: async (data) => sdk.admin.returnReason.update(id, data, query),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: returnReasonsQueryKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: returnReasonsQueryKeys.detail(data.return_reason.id, query),
})
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
export const useDeleteReturnReason = (
id: string,
options?: UseMutationOptions<
HttpTypes.AdminReturnReasonDeleteResponse,
Error,
void
>
) => {
return useMutation({
mutationFn: () => sdk.admin.returnReason.delete(id),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: returnReasonsQueryKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: returnReasonsQueryKeys.detail(id),
})
queryClient.invalidateQueries({
queryKey: returnReasonsQueryKeys.details(),
})
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
@@ -7,6 +7,7 @@ export * from "./use-product-table-columns"
export * from "./use-product-tag-table-columns"
export * from "./use-product-type-table-columns"
export * from "./use-region-table-columns"
export * from "./use-return-reason-table-columns"
export * from "./use-sales-channel-table-columns"
export * from "./use-shipping-option-table-columns"
export * from "./use-tax-rates-table-columns"
@@ -0,0 +1,25 @@
import { HttpTypes } from "@medusajs/types"
import { createColumnHelper } from "@tanstack/react-table"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import { TextCell } from "../../../components/table/table-cells/common/text-cell"
const columnHelper = createColumnHelper<HttpTypes.AdminReturnReason>()
export const useReturnReasonTableColumns = () => {
const { t } = useTranslation()
return useMemo(
() => [
columnHelper.accessor("value", {
header: () => t("fields.value"),
cell: ({ getValue }) => <TextCell text={getValue()} />,
}),
columnHelper.accessor("label", {
header: () => t("fields.createdAt"),
cell: ({ getValue }) => <TextCell text={getValue()} />,
}),
],
[t]
)
}
@@ -7,6 +7,7 @@ export * from "./use-product-table-query"
export * from "./use-product-tag-table-query"
export * from "./use-product-type-table-query"
export * from "./use-region-table-query"
export * from "./use-return-reason-table-query"
export * from "./use-sales-channel-table-query"
export * from "./use-shipping-option-table-query"
export * from "./use-tax-rate-table-query"
@@ -0,0 +1,32 @@
import { HttpTypes } from "@medusajs/types"
import { useQueryParams } from "../../use-query-params"
type UseReturnReasonTableQueryProps = {
prefix?: string
pageSize?: number
}
export const useReturnReasonTableQuery = ({
prefix,
pageSize = 20,
}: UseReturnReasonTableQueryProps) => {
const queryObject = useQueryParams(
["offset", "q", "order", "created_at", "updated_at"],
prefix
)
const { offset, q, order, created_at, updated_at } = queryObject
const searchParams: HttpTypes.AdminReturnReasonListParams = {
limit: pageSize,
offset: offset ? Number(offset) : 0,
order,
created_at: created_at ? JSON.parse(created_at) : undefined,
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
q,
}
return {
searchParams,
raw: queryObject,
}
}
@@ -2102,11 +2102,32 @@
"returnReasons": {
"domain": "Return Reasons",
"calloutHint": "Manage the reasons to categorize returns.",
"deleteReasonWarning": "You are about to delete the return reason {{label}}. This action cannot be undone.",
"createReason": "Create Return Reason",
"createReasonHint": "Create a new return reason to categorize returns.",
"editReason": "Edit Return Reason",
"valueTooltip": "The value should be a unique identifier for the return reason."
"create": {
"header": "Add Return Reason",
"subtitle": "Specify the most common reasons for returns.",
"hint": "Create a new return reason to categorize returns.",
"successToast": "Return reason {{label}} was successfully created."
},
"edit": {
"successToast": "Return reason {{label}} was successfully updated."
},
"delete": {
"confirmation": "You are about to delete the return reason {{label}}. This action cannot be undone.",
"successToast": "Return reason {{label}} was successfully deleted."
},
"fields": {
"value": {
"label": "Value",
"placeholder": "wrong_size",
"tooltip": "The value should be a unique identifier for the return reason."
},
"label": { "label": "Label", "placeholder": "Wrong size" },
"description": {
"label": "Descriptions",
"placeholder": "Customer received the wrong size"
}
}
},
"login": {
"forgotPassword": "Forgot password? - <0>Reset</0>",
@@ -1293,6 +1293,20 @@ export const RouteMap: RouteObject[] = [
},
],
},
{
path: "return-reasons",
element: <Outlet />,
handle: {
crumb: () => "Return Reasons",
},
children: [
{
path: "",
lazy: () =>
import("../../routes/return-reasons/return-reason-list"),
},
],
},
...SettingsExtensions,
],
},
@@ -0,0 +1,40 @@
import { AdminReturnReason } from "@medusajs/types"
import { toast, usePrompt } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { useDeleteReturnReason } from "../../../../hooks/api/return-reasons"
export const useDeleteReturnReasonAction = ({
id,
label,
}: AdminReturnReason) => {
const { t } = useTranslation()
const prompt = usePrompt()
const { mutateAsync } = useDeleteReturnReason(id)
const handleDelete = async () => {
const result = await prompt({
title: t("general.areYouSure"),
description: t("returnReasons.delete.confirmation", {
label,
}),
confirmText: t("actions.delete"),
cancelText: t("actions.cancel"),
})
if (!result) {
return
}
await mutateAsync(undefined, {
onSuccess: () => {
toast.success(t("returnReasons.delete.successToast", { label }))
},
onError: (e) => {
toast.error(e.message)
},
})
}
return handleDelete
}
@@ -0,0 +1 @@
export * from "./return-reason-list-table"
@@ -0,0 +1,127 @@
import { 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 { useReturnReasons } from "../../../../../hooks/api/return-reasons"
import { useReturnReasonTableColumns } from "../../../../../hooks/table/columns"
import { useReturnReasonTableQuery } from "../../../../../hooks/table/query"
import { useDataTable } from "../../../../../hooks/use-data-table"
import { useDeleteReturnReasonAction } from "../../../common/hooks/use-delete-return-reason-action"
import { returnReasonListLoader } from "../../loader"
const PAGE_SIZE = 20
export const ReturnReasonListTable = () => {
const { t } = useTranslation()
const { searchParams, raw } = useReturnReasonTableQuery({
pageSize: PAGE_SIZE,
})
const initialData = useLoaderData() as Awaited<
ReturnType<typeof returnReasonListLoader>
>
const { return_reasons, count, isPending, isError, error } = useReturnReasons(
searchParams,
{
initialData,
placeholderData: keepPreviousData,
}
)
const columns = useColumns()
const { table } = useDataTable({
data: return_reasons,
columns,
count,
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("returnReasons.domain")}</Heading>
<Button variant="secondary" size="small" asChild>
<Link to="create">{t("actions.create")}</Link>
</Button>
</div>
<DataTable
table={table}
queryObject={raw}
count={count}
isLoading={isPending}
columns={columns}
pageSize={PAGE_SIZE}
pagination
/>
</Container>
)
}
type ReturnReasonRowActionsProps = {
returnReason: HttpTypes.AdminReturnReason
}
const ReturnReasonRowActions = ({
returnReason,
}: ReturnReasonRowActionsProps) => {
const { t } = useTranslation()
const handleDelete = useDeleteReturnReasonAction(returnReason)
return (
<ActionMenu
groups={[
/* {
actions: [
{
icon: <PencilSquare />,
label: t("actions.edit"),
to: `${returnReason.id}/edit`,
},
],
}, */
{
actions: [
{
icon: <Trash />,
label: t("actions.delete"),
onClick: handleDelete,
},
],
},
]}
/>
)
}
const columnHelper = createColumnHelper<HttpTypes.AdminReturnReason>()
const useColumns = () => {
const base = useReturnReasonTableColumns()
return useMemo(
() => [
...base,
columnHelper.display({
id: "actions",
cell: ({ row }) => (
<ReturnReasonRowActions returnReason={row.original} />
),
}),
],
[base]
)
}
@@ -0,0 +1,2 @@
export { returnReasonListLoader as loader } from "./loader"
export { ReturnReasonList as Component } from "./return-reason-list"
@@ -0,0 +1,21 @@
import {
AdminReturnReasonListParams,
AdminReturnReasonListResponse,
} from "@medusajs/types"
import { returnReasonsQueryKeys } from "../../../hooks/api/return-reasons"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
const returnReasonListQuery = (query?: AdminReturnReasonListParams) => ({
queryKey: returnReasonsQueryKeys.list(query),
queryFn: async () => sdk.admin.returnReason.list(query),
})
export const returnReasonListLoader = async () => {
const query = returnReasonListQuery()
return (
queryClient.getQueryData<AdminReturnReasonListResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
}
@@ -0,0 +1,21 @@
import { SingleColumnPage } from "../../../components/layout/pages"
import { ReturnReasonListTable } from "./components/return-reason-list-table"
import after from "virtual:medusa/widgets/return_reason/list/after"
import before from "virtual:medusa/widgets/return_reason/list/before"
export const ReturnReasonList = () => {
return (
<SingleColumnPage
showMetadata={false}
showJSON={false}
hasOutlet
widgets={{
after,
before,
}}
>
<ReturnReasonListTable />
</SingleColumnPage>
)
}
@@ -1 +1 @@
export { Settings as Component } from "./settings";
export { Settings as Component } from "./settings"