feat: create return reason (#8516)
* feat: create and edit return reasons * add prop to hide data table header * make return reasons searchable * hide table header
This commit is contained in:
@@ -12,3 +12,4 @@
|
|||||||
- "@sradevski"
|
- "@sradevski"
|
||||||
- "@edast"
|
- "@edast"
|
||||||
- "@thetutlage"
|
- "@thetutlage"
|
||||||
|
- "@christiananese"
|
||||||
|
|||||||
+62
-55
@@ -52,6 +52,10 @@ export interface DataTableRootProps<TData> {
|
|||||||
* Whether the table is empty due to no results from the active query
|
* Whether the table is empty due to no results from the active query
|
||||||
*/
|
*/
|
||||||
noResults?: boolean
|
noResults?: boolean
|
||||||
|
/**
|
||||||
|
* Whether to display the tables header
|
||||||
|
*/
|
||||||
|
noHeader?: boolean
|
||||||
/**
|
/**
|
||||||
* The layout of the table
|
* The layout of the table
|
||||||
*/
|
*/
|
||||||
@@ -80,6 +84,7 @@ export const DataTableRoot = <TData,>({
|
|||||||
commands,
|
commands,
|
||||||
count = 0,
|
count = 0,
|
||||||
noResults = false,
|
noResults = false,
|
||||||
|
noHeader = false,
|
||||||
layout = "fit",
|
layout = "fit",
|
||||||
}: DataTableRootProps<TData>) => {
|
}: DataTableRootProps<TData>) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -133,64 +138,66 @@ export const DataTableRoot = <TData,>({
|
|||||||
>
|
>
|
||||||
{!noResults ? (
|
{!noResults ? (
|
||||||
<Table className="relative w-full">
|
<Table className="relative w-full">
|
||||||
<Table.Header className="border-t-0">
|
{!noHeader && (
|
||||||
{table.getHeaderGroups().map((headerGroup) => {
|
<Table.Header className="border-t-0">
|
||||||
return (
|
{table.getHeaderGroups().map((headerGroup) => {
|
||||||
<Table.Row
|
return (
|
||||||
key={headerGroup.id}
|
<Table.Row
|
||||||
className={clx({
|
key={headerGroup.id}
|
||||||
"relative border-b-0 [&_th:last-of-type]:w-[1%] [&_th:last-of-type]:whitespace-nowrap":
|
className={clx({
|
||||||
hasActions,
|
"relative border-b-0 [&_th:last-of-type]:w-[1%] [&_th:last-of-type]:whitespace-nowrap":
|
||||||
"[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap":
|
hasActions,
|
||||||
hasSelect,
|
"[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap":
|
||||||
})}
|
hasSelect,
|
||||||
>
|
})}
|
||||||
{headerGroup.headers.map((header, index) => {
|
>
|
||||||
const isActionHeader = header.id === "actions"
|
{headerGroup.headers.map((header, index) => {
|
||||||
const isSelectHeader = header.id === "select"
|
const isActionHeader = header.id === "actions"
|
||||||
const isSpecialHeader = isActionHeader || isSelectHeader
|
const isSelectHeader = header.id === "select"
|
||||||
|
const isSpecialHeader = isActionHeader || isSelectHeader
|
||||||
|
|
||||||
const firstHeader = headerGroup.headers.findIndex(
|
const firstHeader = headerGroup.headers.findIndex(
|
||||||
(h) => h.id !== "select"
|
(h) => h.id !== "select"
|
||||||
)
|
)
|
||||||
const isFirstHeader =
|
const isFirstHeader =
|
||||||
firstHeader !== -1
|
firstHeader !== -1
|
||||||
? header.id === headerGroup.headers[firstHeader].id
|
? header.id === headerGroup.headers[firstHeader].id
|
||||||
: index === 0
|
: index === 0
|
||||||
|
|
||||||
const isStickyHeader = isSelectHeader || isFirstHeader
|
const isStickyHeader = isSelectHeader || isFirstHeader
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
data-table-header-id={header.id}
|
data-table-header-id={header.id}
|
||||||
key={header.id}
|
key={header.id}
|
||||||
style={{
|
style={{
|
||||||
width: !isSpecialHeader
|
width: !isSpecialHeader
|
||||||
? `${colWidth}%`
|
? `${colWidth}%`
|
||||||
: undefined,
|
: undefined,
|
||||||
}}
|
}}
|
||||||
className={clx({
|
className={clx({
|
||||||
"bg-ui-bg-base sticky left-0 after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
|
"bg-ui-bg-base sticky left-0 after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
|
||||||
isStickyHeader,
|
isStickyHeader,
|
||||||
"left-[68px]":
|
"left-[68px]":
|
||||||
isStickyHeader && hasSelect && !isSelectHeader,
|
isStickyHeader && hasSelect && !isSelectHeader,
|
||||||
"after:bg-ui-border-base":
|
"after:bg-ui-border-base":
|
||||||
showStickyBorder &&
|
showStickyBorder &&
|
||||||
isStickyHeader &&
|
isStickyHeader &&
|
||||||
!isSpecialHeader,
|
!isSpecialHeader,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{flexRender(
|
{flexRender(
|
||||||
header.column.columnDef.header,
|
header.column.columnDef.header,
|
||||||
header.getContext()
|
header.getContext()
|
||||||
)}
|
)}
|
||||||
</Table.HeaderCell>
|
</Table.HeaderCell>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
|
)}
|
||||||
<Table.Body className="border-b-0">
|
<Table.Body className="border-b-0">
|
||||||
{table.getRowModel().rows.map((row) => {
|
{table.getRowModel().rows.map((row) => {
|
||||||
const to = navigateTo ? navigateTo(row) : undefined
|
const to = navigateTo ? navigateTo(row) : undefined
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export const DataTable = <TData,>({
|
|||||||
queryObject = {},
|
queryObject = {},
|
||||||
pageSize,
|
pageSize,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
noHeader = false,
|
||||||
layout = "fit",
|
layout = "fit",
|
||||||
noRecords: noRecordsProps = {},
|
noRecords: noRecordsProps = {},
|
||||||
}: DataTableProps<TData>) => {
|
}: DataTableProps<TData>) => {
|
||||||
@@ -84,6 +85,7 @@ export const DataTable = <TData,>({
|
|||||||
navigateTo={navigateTo}
|
navigateTo={navigateTo}
|
||||||
commands={commands}
|
commands={commands}
|
||||||
noResults={noResults}
|
noResults={noResults}
|
||||||
|
noHeader={noHeader}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+14
-9
@@ -1,25 +1,30 @@
|
|||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Badge } from "@medusajs/ui"
|
||||||
import { createColumnHelper } from "@tanstack/react-table"
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
import { TextCell } from "../../../components/table/table-cells/common/text-cell"
|
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<HttpTypes.AdminReturnReason>()
|
const columnHelper = createColumnHelper<HttpTypes.AdminReturnReason>()
|
||||||
|
|
||||||
export const useReturnReasonTableColumns = () => {
|
export const useReturnReasonTableColumns = () => {
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => [
|
() => [
|
||||||
columnHelper.accessor("value", {
|
columnHelper.accessor("value", {
|
||||||
header: () => t("fields.value"),
|
cell: ({ getValue }) => <Badge size="2xsmall">{getValue()}</Badge>,
|
||||||
cell: ({ getValue }) => <TextCell text={getValue()} />,
|
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("label", {
|
columnHelper.accessor("label", {
|
||||||
header: () => t("fields.createdAt"),
|
cell: ({ row }) => {
|
||||||
cell: ({ getValue }) => <TextCell text={getValue()} />,
|
const { label, description } = row.original
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full flex-col justify-center py-4">
|
||||||
|
<span className="truncate font-medium">{label}</span>
|
||||||
|
<span className="truncate">
|
||||||
|
{description ? description : "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
[t]
|
[]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,8 @@
|
|||||||
"goToPublishableApiKeys": "Publishable API Keys",
|
"goToPublishableApiKeys": "Publishable API Keys",
|
||||||
"goToSecretApiKeys": "Secret API Keys",
|
"goToSecretApiKeys": "Secret API Keys",
|
||||||
"goToWorkflows": "Workflows",
|
"goToWorkflows": "Workflows",
|
||||||
"goToProfile": "Profile"
|
"goToProfile": "Profile",
|
||||||
|
"goToReturnReasons": "Return reasons"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menus": {
|
"menus": {
|
||||||
@@ -2104,6 +2105,7 @@
|
|||||||
},
|
},
|
||||||
"returnReasons": {
|
"returnReasons": {
|
||||||
"domain": "Return Reasons",
|
"domain": "Return Reasons",
|
||||||
|
"subtitle": "Manage reasons for returned items.",
|
||||||
"calloutHint": "Manage the reasons to categorize returns.",
|
"calloutHint": "Manage the reasons to categorize returns.",
|
||||||
"editReason": "Edit Return Reason",
|
"editReason": "Edit Return Reason",
|
||||||
"create": {
|
"create": {
|
||||||
@@ -2113,6 +2115,8 @@
|
|||||||
"successToast": "Return reason {{label}} was successfully created."
|
"successToast": "Return reason {{label}} was successfully created."
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
|
"header": "Edit Return Reason",
|
||||||
|
"subtitle": "Edit the value of the return reason.",
|
||||||
"successToast": "Return reason {{label}} was successfully updated."
|
"successToast": "Return reason {{label}} was successfully updated."
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
|
|||||||
@@ -264,6 +264,14 @@ export const useGlobalShortcuts = () => {
|
|||||||
type: "settingShortcut",
|
type: "settingShortcut",
|
||||||
to: "/settings/locations",
|
to: "/settings/locations",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
keys: {
|
||||||
|
Mac: ["G", ",", "M"],
|
||||||
|
},
|
||||||
|
label: t("app.keyboardShortcuts.settings.goToReturnReasons"),
|
||||||
|
type: "settingShortcut",
|
||||||
|
to: "/settings/return-reasons",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
keys: {
|
keys: {
|
||||||
Mac: ["G", ",", "J"],
|
Mac: ["G", ",", "J"],
|
||||||
|
|||||||
@@ -1304,6 +1304,28 @@ export const RouteMap: RouteObject[] = [
|
|||||||
path: "",
|
path: "",
|
||||||
lazy: () =>
|
lazy: () =>
|
||||||
import("../../routes/return-reasons/return-reason-list"),
|
import("../../routes/return-reasons/return-reason-list"),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "create",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../routes/return-reasons/return-reason-create"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: ":id",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "edit",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../routes/return-reasons/return-reason-edit"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./return-reason-create-form"
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { Button, Heading, Input, Text, Textarea, 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 { useCreateReturnReason } from "../../../../../hooks/api/return-reasons"
|
||||||
|
|
||||||
|
const ReturnReasonCreateSchema = z.object({
|
||||||
|
value: z.string().min(1),
|
||||||
|
label: z.string().min(1),
|
||||||
|
description: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ReturnReasonCreateForm = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof ReturnReasonCreateSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
value: "",
|
||||||
|
label: "",
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(ReturnReasonCreateSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useCreateReturnReason()
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
await mutateAsync(data, {
|
||||||
|
onSuccess: ({ return_reason }) => {
|
||||||
|
toast.success(
|
||||||
|
t("returnReasons.create.successToast", {
|
||||||
|
label: return_reason.label,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
handleSuccess(`../`)
|
||||||
|
},
|
||||||
|
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("returnReasons.create.header")}</Heading>
|
||||||
|
</RouteFocusModal.Title>
|
||||||
|
<RouteFocusModal.Description asChild>
|
||||||
|
<Text size="small" className="text-ui-fg-subtle">
|
||||||
|
{t("returnReasons.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
|
||||||
|
tooltip={t("returnReasons.fields.value.tooltip")}
|
||||||
|
>
|
||||||
|
{t("returnReasons.fields.value.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t(
|
||||||
|
"returnReasons.fields.value.placeholder"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="label"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("returnReasons.fields.label.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t(
|
||||||
|
"returnReasons.fields.label.placeholder"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>
|
||||||
|
{t("returnReasons.fields.description.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Textarea
|
||||||
|
{...field}
|
||||||
|
placeholder={t(
|
||||||
|
"returnReasons.fields.description.placeholder"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</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 { ReturnReasonCreate as Component } from "./return-reason-create"
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import { RouteFocusModal } from "../../../components/modals"
|
||||||
|
import { ReturnReasonCreateForm } from "./components/return-reason-create-form"
|
||||||
|
|
||||||
|
export const ReturnReasonCreate = () => {
|
||||||
|
return (
|
||||||
|
<RouteFocusModal>
|
||||||
|
<ReturnReasonCreateForm />
|
||||||
|
</RouteFocusModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./return-reason-edit-form"
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, Input, Textarea, 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 { useUpdateReturnReason } from "../../../../../hooks/api/return-reasons"
|
||||||
|
|
||||||
|
type ReturnReasonEditFormProps = {
|
||||||
|
returnReason: HttpTypes.AdminReturnReason
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReturnReasonEditSchema = z.object({
|
||||||
|
value: z.string().min(1),
|
||||||
|
label: z.string().min(1),
|
||||||
|
description: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ReturnReasonEditForm = ({
|
||||||
|
returnReason,
|
||||||
|
}: ReturnReasonEditFormProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof ReturnReasonEditSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
value: returnReason.value,
|
||||||
|
label: returnReason.label,
|
||||||
|
description: returnReason.description ?? undefined,
|
||||||
|
},
|
||||||
|
resolver: zodResolver(ReturnReasonEditSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useUpdateReturnReason(returnReason.id)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
await mutateAsync(data, {
|
||||||
|
onSuccess: ({ return_reason }) => {
|
||||||
|
toast.success(
|
||||||
|
t("returnReasons.edit.successToast", {
|
||||||
|
label: return_reason.label,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
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 gap-y-4 overflow-auto">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="value"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label tooltip={t("returnReasons.fields.value.tooltip")}>
|
||||||
|
{t("returnReasons.fields.value.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t("returnReasons.fields.value.placeholder")}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="label"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("returnReasons.fields.label.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t("returnReasons.fields.label.placeholder")}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>
|
||||||
|
{t("returnReasons.fields.description.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Textarea
|
||||||
|
{...field}
|
||||||
|
placeholder={t(
|
||||||
|
"returnReasons.fields.description.placeholder"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</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 { ReturnReasonEdit as Component } from "./return-reason-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 { useReturnReason } from "../../../hooks/api/return-reasons"
|
||||||
|
import { ReturnReasonEditForm } from "./components/return-reason-edit-form"
|
||||||
|
|
||||||
|
export const ReturnReasonEdit = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { return_reason, isPending, isError, error } = useReturnReason(id!)
|
||||||
|
|
||||||
|
const ready = !isPending && !!return_reason
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer>
|
||||||
|
<RouteDrawer.Header>
|
||||||
|
<RouteDrawer.Title asChild>
|
||||||
|
<Heading>{t("returnReasons.edit.header")}</Heading>
|
||||||
|
</RouteDrawer.Title>
|
||||||
|
<RouteDrawer.Description className="sr-only">
|
||||||
|
{t("returnReasons.edit.subtitle")}
|
||||||
|
</RouteDrawer.Description>
|
||||||
|
</RouteDrawer.Header>
|
||||||
|
{ready && <ReturnReasonEditForm returnReason={return_reason} />}
|
||||||
|
</RouteDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
+13
-12
@@ -1,11 +1,11 @@
|
|||||||
import { Trash } from "@medusajs/icons"
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { Button, Container, Heading } from "@medusajs/ui"
|
import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||||
import { keepPreviousData } from "@tanstack/react-query"
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
import { createColumnHelper } from "@tanstack/react-table"
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Link, useLoaderData } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
|
|
||||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
import { DataTable } from "../../../../../components/table/data-table"
|
import { DataTable } from "../../../../../components/table/data-table"
|
||||||
@@ -14,7 +14,6 @@ import { useReturnReasonTableColumns } from "../../../../../hooks/table/columns"
|
|||||||
import { useReturnReasonTableQuery } from "../../../../../hooks/table/query"
|
import { useReturnReasonTableQuery } from "../../../../../hooks/table/query"
|
||||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||||
import { useDeleteReturnReasonAction } from "../../../common/hooks/use-delete-return-reason-action"
|
import { useDeleteReturnReasonAction } from "../../../common/hooks/use-delete-return-reason-action"
|
||||||
import { returnReasonListLoader } from "../../loader"
|
|
||||||
|
|
||||||
const PAGE_SIZE = 20
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
@@ -24,14 +23,9 @@ export const ReturnReasonListTable = () => {
|
|||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
})
|
})
|
||||||
|
|
||||||
const initialData = useLoaderData() as Awaited<
|
|
||||||
ReturnType<typeof returnReasonListLoader>
|
|
||||||
>
|
|
||||||
|
|
||||||
const { return_reasons, count, isPending, isError, error } = useReturnReasons(
|
const { return_reasons, count, isPending, isError, error } = useReturnReasons(
|
||||||
searchParams,
|
searchParams,
|
||||||
{
|
{
|
||||||
initialData,
|
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -53,7 +47,12 @@ export const ReturnReasonListTable = () => {
|
|||||||
return (
|
return (
|
||||||
<Container className="divide-y px-0 py-0">
|
<Container className="divide-y px-0 py-0">
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<Heading>{t("returnReasons.domain")}</Heading>
|
<div>
|
||||||
|
<Heading>{t("returnReasons.domain")}</Heading>
|
||||||
|
<Text className="text-ui-fg-subtle" size="small">
|
||||||
|
{t("returnReasons.subtitle")}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
<Button variant="secondary" size="small" asChild>
|
<Button variant="secondary" size="small" asChild>
|
||||||
<Link to="create">{t("actions.create")}</Link>
|
<Link to="create">{t("actions.create")}</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -65,7 +64,9 @@ export const ReturnReasonListTable = () => {
|
|||||||
isLoading={isPending}
|
isLoading={isPending}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pageSize={PAGE_SIZE}
|
pageSize={PAGE_SIZE}
|
||||||
|
noHeader={true}
|
||||||
pagination
|
pagination
|
||||||
|
search
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
@@ -84,7 +85,7 @@ const ReturnReasonRowActions = ({
|
|||||||
return (
|
return (
|
||||||
<ActionMenu
|
<ActionMenu
|
||||||
groups={[
|
groups={[
|
||||||
/* {
|
{
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
icon: <PencilSquare />,
|
icon: <PencilSquare />,
|
||||||
@@ -92,7 +93,7 @@ const ReturnReasonRowActions = ({
|
|||||||
to: `${returnReason.id}/edit`,
|
to: `${returnReason.id}/edit`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}, */
|
},
|
||||||
{
|
{
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2489,25 +2489,29 @@ export interface FilterableOrderItemProps
|
|||||||
*/
|
*/
|
||||||
export interface FilterableOrderReturnReasonProps
|
export interface FilterableOrderReturnReasonProps
|
||||||
extends BaseFilterable<FilterableOrderReturnReasonProps> {
|
extends BaseFilterable<FilterableOrderReturnReasonProps> {
|
||||||
|
/**
|
||||||
|
* Find return reasons through this search term
|
||||||
|
*/
|
||||||
|
q?: string
|
||||||
/**
|
/**
|
||||||
* The IDs to filter the return reasons by.
|
* The IDs to filter the return reasons by.
|
||||||
*/
|
*/
|
||||||
id?: string | string[]
|
id?: string | string[] | OperatorMap<string | string[]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the return reason by their value.
|
* Filter the return reason by their value.
|
||||||
*/
|
*/
|
||||||
value?: string | string[]
|
value?: string | string[] | OperatorMap<string | string[]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the return reason by their label.
|
* Filter the return reason by their label.
|
||||||
*/
|
*/
|
||||||
label?: string
|
label?: string | OperatorMap<string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the return reason by their description.
|
* Filter the return reason by their description.
|
||||||
*/
|
*/
|
||||||
description?: string
|
description?: string | OperatorMap<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DAL } from "@medusajs/types"
|
import { DAL } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
DALUtils,
|
DALUtils,
|
||||||
|
Searchable,
|
||||||
createPsqlIndexStatementHelper,
|
createPsqlIndexStatementHelper,
|
||||||
generateEntityId,
|
generateEntityId,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
@@ -46,10 +47,12 @@ export default class ReturnReason {
|
|||||||
@PrimaryKey({ columnType: "text" })
|
@PrimaryKey({ columnType: "text" })
|
||||||
id: string
|
id: string
|
||||||
|
|
||||||
|
@Searchable()
|
||||||
@Property({ columnType: "text" })
|
@Property({ columnType: "text" })
|
||||||
@ValueIndex.MikroORMIndex()
|
@ValueIndex.MikroORMIndex()
|
||||||
value: string
|
value: string
|
||||||
|
|
||||||
|
@Searchable()
|
||||||
@Property({ columnType: "text" })
|
@Property({ columnType: "text" })
|
||||||
label: string
|
label: string
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ export default class ReturnReason {
|
|||||||
cascade: [Cascade.PERSIST],
|
cascade: [Cascade.PERSIST],
|
||||||
})
|
})
|
||||||
parent_return_reason?: Rel<ReturnReason> | null
|
parent_return_reason?: Rel<ReturnReason> | null
|
||||||
|
Searchable
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => ReturnReason,
|
() => ReturnReason,
|
||||||
(return_reason) => return_reason.parent_return_reason,
|
(return_reason) => return_reason.parent_return_reason,
|
||||||
|
|||||||
Reference in New Issue
Block a user