feat(admin-next) discounts list page (#6490)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa): add `order` param to discounts list
|
||||||
@@ -187,7 +187,14 @@
|
|||||||
"domain": "Draft Orders"
|
"domain": "Draft Orders"
|
||||||
},
|
},
|
||||||
"discounts": {
|
"discounts": {
|
||||||
"domain": "Discounts"
|
"domain": "Discounts",
|
||||||
|
"deleteWarning": "You are about to delete the discount {{title}}. This action cannot be undone.",
|
||||||
|
"discountStatus": {
|
||||||
|
"scheduled": "Scheduled",
|
||||||
|
"expired": "Expired",
|
||||||
|
"active": "Active",
|
||||||
|
"disabled": "Disabled"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"pricing": {
|
"pricing": {
|
||||||
"domain": "Pricing"
|
"domain": "Pricing"
|
||||||
@@ -355,6 +362,11 @@
|
|||||||
"sales_channels": "Sales Channels",
|
"sales_channels": "Sales Channels",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
|
"value": "Value",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"dynamic": "Dynamic",
|
||||||
|
"normal": "Normal",
|
||||||
|
"totalRedemptions": "Total Redemptions",
|
||||||
"countries": "Countries",
|
"countries": "Countries",
|
||||||
"paymentProviders": "Payment Providers",
|
"paymentProviders": "Payment Providers",
|
||||||
"fulfillmentProviders": "Fulfillment Providers",
|
"fulfillmentProviders": "Fulfillment Providers",
|
||||||
@@ -392,6 +404,7 @@
|
|||||||
"items": "Items",
|
"items": "Items",
|
||||||
"salesChannel": "Sales Channel",
|
"salesChannel": "Sales Channel",
|
||||||
"region": "Region",
|
"region": "Region",
|
||||||
|
"discount": "Discount",
|
||||||
"role": "Role",
|
"role": "Role",
|
||||||
"sent": "Sent",
|
"sent": "Sent",
|
||||||
"salesChannels": "Sales Channels",
|
"salesChannels": "Sales Channels",
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
import type { Discount } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
type DiscountCellProps = {
|
||||||
|
discount: Discount
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CodeCell = ({ discount }: DiscountCellProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||||
|
{/*// TODO: border color inversion*/}
|
||||||
|
<span className="bg-ui-tag-neutral-bg truncate rounded-md border border-neutral-200 p-1 text-xs">
|
||||||
|
{discount.code}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CodeHeader = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className=" flex h-full w-full items-center ">
|
||||||
|
<span>{t("fields.description")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./code-cell"
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import type { Discount } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
type DiscountCellProps = {
|
||||||
|
discount: Discount
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DescriptionCell = ({ discount }: DiscountCellProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||||
|
<span className="truncate">{discount.rule.description}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DescriptionHeader = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className=" flex h-full w-full items-center ">
|
||||||
|
<span>{t("fields.description")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./description-cell"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./redemption-cell.tsx"
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
type DiscountCellProps = {
|
||||||
|
redemptions: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RedemptionCell = ({ redemptions }: DiscountCellProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center justify-end gap-x-3 text-right">
|
||||||
|
<span>{redemptions}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RedemptionHeader = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center justify-end text-right">
|
||||||
|
<span className="truncate">{t("fields.totalRedemptions")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./status-cell"
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
import { Discount } from "@medusajs/medusa"
|
||||||
|
import { end, parse } from "iso8601-duration"
|
||||||
|
|
||||||
|
import { StatusCell as StatusCell_ } from "../../common/status-cell"
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
type DiscountCellProps = {
|
||||||
|
discount: Discount
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PromotionStatus {
|
||||||
|
SCHEDULED = "SCHEDULED",
|
||||||
|
EXPIRED = "EXPIRED",
|
||||||
|
ACTIVE = "ACTIVE",
|
||||||
|
DISABLED = "DISABLED",
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDiscountStatus = (discount: Discount) => {
|
||||||
|
if (discount.is_disabled) {
|
||||||
|
return PromotionStatus.DISABLED
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date()
|
||||||
|
if (new Date(discount.starts_at) > date) {
|
||||||
|
return PromotionStatus.SCHEDULED
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(discount.ends_at && new Date(discount.ends_at) < date) ||
|
||||||
|
(discount.valid_duration &&
|
||||||
|
date >
|
||||||
|
end(parse(discount.valid_duration), new Date(discount.starts_at))) ||
|
||||||
|
discount.usage_count === discount.usage_limit
|
||||||
|
) {
|
||||||
|
return PromotionStatus.EXPIRED
|
||||||
|
}
|
||||||
|
|
||||||
|
return PromotionStatus.ACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusCell = ({ discount }: DiscountCellProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const [color, text] = {
|
||||||
|
[PromotionStatus.DISABLED]: [
|
||||||
|
"grey",
|
||||||
|
t("discounts.discountStatus.disabled"),
|
||||||
|
],
|
||||||
|
[PromotionStatus.ACTIVE]: ["green", t("discounts.discountStatus.active")],
|
||||||
|
[PromotionStatus.SCHEDULED]: [
|
||||||
|
"orange",
|
||||||
|
t("discounts.discountStatus.scheduled"),
|
||||||
|
],
|
||||||
|
[PromotionStatus.EXPIRED]: ["red", t("discounts.discountStatus.expired")],
|
||||||
|
}[getDiscountStatus(discount)] as [
|
||||||
|
"grey" | "orange" | "green" | "red",
|
||||||
|
string
|
||||||
|
]
|
||||||
|
|
||||||
|
return <StatusCell_ color={color}>{text}</StatusCell_>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusHeader = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className=" flex h-full w-full items-center ">
|
||||||
|
<span>{t("fields.status")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./value-cell.tsx"
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
import { DiscountRule } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { MoneyAmountCell } from "../../common/money-amount-cell"
|
||||||
|
|
||||||
|
type DiscountCellProps = {
|
||||||
|
rule: DiscountRule
|
||||||
|
currencyCode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ValueCell = ({ currencyCode, rule }: DiscountCellProps) => {
|
||||||
|
const isFixed = rule.type === "fixed"
|
||||||
|
const isPercentage = rule.type === "percentage"
|
||||||
|
const isFreeShipping = rule.type === "free_shipping"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||||
|
{isFreeShipping && <span>Free shipping</span>}
|
||||||
|
{isPercentage && <span className="">{rule.value}%</span>}
|
||||||
|
{isFixed && (
|
||||||
|
<MoneyAmountCell currencyCode={currencyCode} amount={rule.value} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ValueHeader = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className=" flex h-full w-full items-center ">
|
||||||
|
<span>{t("fields.value")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useMemo } from "react"
|
||||||
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
|
||||||
|
|
||||||
|
import type { Discount } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
import {
|
||||||
|
CodeCell,
|
||||||
|
CodeHeader,
|
||||||
|
} from "../../../components/table/table-cells/discount/code-cell"
|
||||||
|
import {
|
||||||
|
DescriptionHeader,
|
||||||
|
DescriptionCell,
|
||||||
|
} from "../../../components/table/table-cells/discount/description-cell"
|
||||||
|
import {
|
||||||
|
ValueCell,
|
||||||
|
ValueHeader,
|
||||||
|
} from "../../../components/table/table-cells/discount/value-cell"
|
||||||
|
import {
|
||||||
|
RedemptionCell,
|
||||||
|
RedemptionHeader,
|
||||||
|
} from "../../../components/table/table-cells/discount/redemption-cell"
|
||||||
|
import {
|
||||||
|
StatusHeader,
|
||||||
|
StatusCell,
|
||||||
|
} from "../../../components/table/table-cells/discount/status-cell"
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<Discount>()
|
||||||
|
|
||||||
|
export const useDiscountTableColumns = () => {
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.display({
|
||||||
|
id: "discount",
|
||||||
|
header: () => <CodeHeader />,
|
||||||
|
cell: ({ row }) => <CodeCell discount={row.original} />,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("rule.description", {
|
||||||
|
header: () => <DescriptionHeader />,
|
||||||
|
cell: ({ row }) => <DescriptionCell discount={row.original} />,
|
||||||
|
}),
|
||||||
|
|
||||||
|
columnHelper.accessor("rule.value", {
|
||||||
|
header: () => <ValueHeader />,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<ValueCell
|
||||||
|
rule={row.original.rule}
|
||||||
|
currencyCode={row.original.regions[0]?.currency_code}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
columnHelper.display({
|
||||||
|
id: "status",
|
||||||
|
header: () => <StatusHeader />,
|
||||||
|
cell: ({ row }) => <StatusCell discount={row.original} />,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("usage_count", {
|
||||||
|
header: () => <RedemptionHeader />,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<RedemptionCell redemptions={row.original.usage_count} />
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
) as ColumnDef<Discount>[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { Filter } from "../../../components/table/data-table"
|
||||||
|
|
||||||
|
export const useDiscountTableFilters = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
let filters: Filter[] = [
|
||||||
|
{ label: t("fields.createdAt"), key: "created_at" },
|
||||||
|
{ label: t("fields.updatedAt"), key: "updated_at" },
|
||||||
|
].map((f) => ({
|
||||||
|
key: f.key,
|
||||||
|
label: f.label,
|
||||||
|
type: "date",
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isDisabledFilter: Filter = {
|
||||||
|
key: "is_disabled",
|
||||||
|
label: t("fields.disabled"),
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t("fields.true"),
|
||||||
|
value: "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("fields.false"),
|
||||||
|
value: "false",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDynamicFilter: Filter = {
|
||||||
|
key: "is_dynamic",
|
||||||
|
label: t("fields.type"),
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t("fields.dynamic"),
|
||||||
|
value: "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("fields.normal"),
|
||||||
|
value: "false",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
filters = [...filters, isDisabledFilter, isDynamicFilter]
|
||||||
|
|
||||||
|
return filters
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { AdminGetDiscountsParams } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
import { useQueryParams } from "../../use-query-params"
|
||||||
|
|
||||||
|
type UseDiscountTableQueryProps = {
|
||||||
|
prefix?: string
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDiscountTableQuery = ({
|
||||||
|
prefix,
|
||||||
|
pageSize = 20,
|
||||||
|
}: UseDiscountTableQueryProps) => {
|
||||||
|
const queryObject = useQueryParams(
|
||||||
|
[
|
||||||
|
"offset",
|
||||||
|
"order",
|
||||||
|
"q",
|
||||||
|
"is_dynamic",
|
||||||
|
"is_disabled",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
],
|
||||||
|
prefix
|
||||||
|
)
|
||||||
|
|
||||||
|
const { offset, order, q, is_dynamic, is_disabled, created_at, updated_at } =
|
||||||
|
queryObject
|
||||||
|
|
||||||
|
const searchParams: AdminGetDiscountsParams = {
|
||||||
|
limit: pageSize,
|
||||||
|
is_disabled: is_disabled ? is_disabled === "true" : undefined,
|
||||||
|
is_dynamic: is_dynamic ? is_dynamic === "true" : undefined,
|
||||||
|
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||||
|
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||||
|
offset: offset ? Number(offset) : 0,
|
||||||
|
order,
|
||||||
|
q,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchParams,
|
||||||
|
raw: queryObject,
|
||||||
|
}
|
||||||
|
}
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
|
import type { Discount } from "@medusajs/medusa"
|
||||||
|
import { Button, Container, Heading, usePrompt } from "@medusajs/ui"
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
|
import { useAdminDeleteDiscount, useAdminDiscounts } from "medusa-react"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { Link, Outlet, useLoaderData } from "react-router-dom"
|
||||||
|
|
||||||
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
|
import { DataTable } from "../../../../../components/table/data-table"
|
||||||
|
import { useDiscountTableFilters } from "../../../../../hooks/table/filters/use-discount-table-filters"
|
||||||
|
import { useDiscountTableColumns } from "../../../../../hooks/table/columns/use-discount-table-columns"
|
||||||
|
import { useDiscountTableQuery } from "../../../../../hooks/table/query/use-discount-table-query"
|
||||||
|
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||||
|
import { discountsLoader } from "../../loader"
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
|
export const DiscountListTable = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const initialData = useLoaderData() as Awaited<
|
||||||
|
ReturnType<ReturnType<typeof discountsLoader>>
|
||||||
|
>
|
||||||
|
|
||||||
|
const { searchParams, raw } = useDiscountTableQuery({ pageSize: PAGE_SIZE })
|
||||||
|
const { discounts, count, isLoading, isError, error } = useAdminDiscounts(
|
||||||
|
{
|
||||||
|
...searchParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initialData,
|
||||||
|
keepPreviousData: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const filters = useDiscountTableFilters()
|
||||||
|
const columns = useColumns()
|
||||||
|
|
||||||
|
const { table } = useDataTable({
|
||||||
|
data: (discounts ?? []) as Discount[],
|
||||||
|
columns,
|
||||||
|
count,
|
||||||
|
enablePagination: true,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
getRowId: (row) => row.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="divide-y p-0">
|
||||||
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
|
<Heading level="h2">{t("discounts.domain")}</Heading>
|
||||||
|
<Button size="small" variant="secondary" asChild>
|
||||||
|
<Link to="create">{t("actions.create")}</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DataTable
|
||||||
|
table={table}
|
||||||
|
columns={columns}
|
||||||
|
count={count}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
filters={filters}
|
||||||
|
search
|
||||||
|
pagination
|
||||||
|
isLoading={isLoading}
|
||||||
|
queryObject={raw}
|
||||||
|
navigateTo={(row) => `${row.original.id}`}
|
||||||
|
orderBy={["code", "created_at", "updated_at"]}
|
||||||
|
/>
|
||||||
|
<Outlet />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DiscountActions = ({ discount }: { discount: Discount }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const prompt = usePrompt()
|
||||||
|
const { mutateAsync } = useAdminDeleteDiscount(discount.id)
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
const res = await prompt({
|
||||||
|
title: t("general.areYouSure"),
|
||||||
|
description: t("discounts.deleteWarning", {
|
||||||
|
title: discount.code,
|
||||||
|
}),
|
||||||
|
confirmText: t("actions.delete"),
|
||||||
|
cancelText: t("actions.cancel"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await mutateAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionMenu
|
||||||
|
groups={[
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
label: t("actions.edit"),
|
||||||
|
to: `/discounts/${discount.id}/edit`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
icon: <Trash />,
|
||||||
|
label: t("actions.delete"),
|
||||||
|
onClick: handleDelete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<Discount>()
|
||||||
|
|
||||||
|
const useColumns = () => {
|
||||||
|
const base = useDiscountTableColumns()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
...base,
|
||||||
|
columnHelper.display({
|
||||||
|
id: "actions",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <DiscountActions discount={row.original} />
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[base]
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./discount-list-table.tsx"
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { DiscountsList as Component } from "./list";
|
export { discountsLoader } from "./loader"
|
||||||
|
export { DiscountsList as Component } from "./list"
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import { Container, Heading } from "@medusajs/ui";
|
import after from "medusa-admin:widgets/discount/list/after"
|
||||||
|
import before from "medusa-admin:widgets/discount/list/before"
|
||||||
|
|
||||||
|
import { DiscountListTable } from "./components/discount-list-table"
|
||||||
|
|
||||||
export const DiscountsList = () => {
|
export const DiscountsList = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col gap-y-2">
|
||||||
<Container>
|
{before.widgets.map((w, i) => (
|
||||||
<Heading>Discounts</Heading>
|
<w.Component key={i} />
|
||||||
</Container>
|
))}
|
||||||
|
<DiscountListTable />
|
||||||
|
{after.widgets.map((w, i) => (
|
||||||
|
<w.Component key={i} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { QueryClient } from "@tanstack/react-query"
|
||||||
|
|
||||||
|
import { Response } from "@medusajs/medusa-js"
|
||||||
|
import { AdminDiscountsListRes } from "@medusajs/medusa"
|
||||||
|
import { adminDiscountKeys } from "medusa-react"
|
||||||
|
|
||||||
|
import { medusa, queryClient } from "../../../lib/medusa"
|
||||||
|
|
||||||
|
const discountsListQuery = () => ({
|
||||||
|
queryKey: adminDiscountKeys.list({ limit: 20, offset: 0 }),
|
||||||
|
queryFn: async () => medusa.admin.discounts.list({ limit: 20, offset: 0 }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const discountsLoader = (client: QueryClient) => {
|
||||||
|
return async () => {
|
||||||
|
const query = discountsListQuery()
|
||||||
|
|
||||||
|
return (
|
||||||
|
queryClient.getQueryData<Response<AdminDiscountsListRes>>(
|
||||||
|
query.queryKey
|
||||||
|
) ?? (await client.fetchQuery(query))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,4 +41,50 @@ export interface AdminGetDiscountsParams {
|
|||||||
* Comma-separated relations that should be expanded in each returned discount.
|
* Comma-separated relations that should be expanded in each returned discount.
|
||||||
*/
|
*/
|
||||||
expand?: string
|
expand?: string
|
||||||
|
/**
|
||||||
|
* A discount field to sort-order the retrieved discounts by.
|
||||||
|
*/
|
||||||
|
order?: string
|
||||||
|
/**
|
||||||
|
* Filter by a creation date range.
|
||||||
|
*/
|
||||||
|
created_at?: {
|
||||||
|
/**
|
||||||
|
* filter by dates less than this date
|
||||||
|
*/
|
||||||
|
lt?: string
|
||||||
|
/**
|
||||||
|
* filter by dates greater than this date
|
||||||
|
*/
|
||||||
|
gt?: string
|
||||||
|
/**
|
||||||
|
* filter by dates less than or equal to this date
|
||||||
|
*/
|
||||||
|
lte?: string
|
||||||
|
/**
|
||||||
|
* filter by dates greater than or equal to this date
|
||||||
|
*/
|
||||||
|
gte?: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Filter by an update date range.
|
||||||
|
*/
|
||||||
|
updated_at?: {
|
||||||
|
/**
|
||||||
|
* filter by dates less than this date
|
||||||
|
*/
|
||||||
|
lt?: string
|
||||||
|
/**
|
||||||
|
* filter by dates greater than this date
|
||||||
|
*/
|
||||||
|
gt?: string
|
||||||
|
/**
|
||||||
|
* filter by dates less than or equal to this date
|
||||||
|
*/
|
||||||
|
lte?: string
|
||||||
|
/**
|
||||||
|
* filter by dates greater than or equal to this date
|
||||||
|
*/
|
||||||
|
gte?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import {
|
|||||||
import { Transform, Type } from "class-transformer"
|
import { Transform, Type } from "class-transformer"
|
||||||
|
|
||||||
import { AdminGetDiscountsDiscountRuleParams } from "../../../../types/discount"
|
import { AdminGetDiscountsDiscountRuleParams } from "../../../../types/discount"
|
||||||
import { extendedFindParamsMixin } from "../../../../types/common"
|
import {
|
||||||
|
DateComparisonOperator,
|
||||||
|
extendedFindParamsMixin,
|
||||||
|
} from "../../../../types/common"
|
||||||
import { Request, Response } from "express"
|
import { Request, Response } from "express"
|
||||||
import { DiscountService } from "../../../../services"
|
import { DiscountService } from "../../../../services"
|
||||||
import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
|
import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
|
||||||
@@ -39,6 +42,51 @@ import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
|
|||||||
* - (query) limit=20 {number} The number of discounts to return
|
* - (query) limit=20 {number} The number of discounts to return
|
||||||
* - (query) offset=0 {number} The number of discounts to skip when retrieving the discounts.
|
* - (query) offset=0 {number} The number of discounts to skip when retrieving the discounts.
|
||||||
* - (query) expand {string} Comma-separated relations that should be expanded in each returned discount.
|
* - (query) expand {string} Comma-separated relations that should be expanded in each returned discount.
|
||||||
|
* - (query) order {string} A discount field to sort-order the retrieved discounts by.
|
||||||
|
* - in: query
|
||||||
|
* name: created_at
|
||||||
|
* description: Filter by a creation date range.
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* lt:
|
||||||
|
* type: string
|
||||||
|
* description: filter by dates less than this date
|
||||||
|
* format: date
|
||||||
|
* gt:
|
||||||
|
* type: string
|
||||||
|
* description: filter by dates greater than this date
|
||||||
|
* format: date
|
||||||
|
* lte:
|
||||||
|
* type: string
|
||||||
|
* description: filter by dates less than or equal to this date
|
||||||
|
* format: date
|
||||||
|
* gte:
|
||||||
|
* type: string
|
||||||
|
* description: filter by dates greater than or equal to this date
|
||||||
|
* format: date
|
||||||
|
* - in: query
|
||||||
|
* name: updated_at
|
||||||
|
* description: Filter by an update date range.
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* lt:
|
||||||
|
* type: string
|
||||||
|
* description: filter by dates less than this date
|
||||||
|
* format: date
|
||||||
|
* gt:
|
||||||
|
* type: string
|
||||||
|
* description: filter by dates greater than this date
|
||||||
|
* format: date
|
||||||
|
* lte:
|
||||||
|
* type: string
|
||||||
|
* description: filter by dates less than or equal to this date
|
||||||
|
* format: date
|
||||||
|
* gte:
|
||||||
|
* type: string
|
||||||
|
* description: filter by dates greater than or equal to this date
|
||||||
|
* format: date
|
||||||
* x-codegen:
|
* x-codegen:
|
||||||
* method: list
|
* method: list
|
||||||
* queryParams: AdminGetDiscountsParams
|
* queryParams: AdminGetDiscountsParams
|
||||||
@@ -167,4 +215,27 @@ export class AdminGetDiscountsParams extends extendedFindParamsMixin({
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Transform(({ value }) => optionalBooleanMapper.get(value))
|
@Transform(({ value }) => optionalBooleanMapper.get(value))
|
||||||
is_disabled?: boolean
|
is_disabled?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date filters to apply on the discounts' `created_at` date.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DateComparisonOperator)
|
||||||
|
created_at?: DateComparisonOperator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date filters to apply on the discounts' `updated_at` date.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DateComparisonOperator)
|
||||||
|
updated_at?: DateComparisonOperator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
order?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import { optionalBooleanMapper } from "../utils/validators/is-boolean"
|
import { optionalBooleanMapper } from "../utils/validators/is-boolean"
|
||||||
import { IsType } from "../utils/validators/is-type"
|
import { IsType } from "../utils/validators/is-type"
|
||||||
import { ExactlyOne } from "./validators/exactly-one"
|
import { ExactlyOne } from "./validators/exactly-one"
|
||||||
|
import { DateComparisonOperator } from "./common"
|
||||||
|
|
||||||
export type QuerySelector = {
|
export type QuerySelector = {
|
||||||
q?: string
|
q?: string
|
||||||
@@ -46,6 +47,16 @@ export class FilterableDiscountProps {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Type(() => AdminGetDiscountsDiscountRuleParams)
|
@Type(() => AdminGetDiscountsDiscountRuleParams)
|
||||||
rule?: AdminGetDiscountsDiscountRuleParams
|
rule?: AdminGetDiscountsDiscountRuleParams
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DateComparisonOperator)
|
||||||
|
created_at?: DateComparisonOperator
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DateComparisonOperator)
|
||||||
|
updated_at?: DateComparisonOperator
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user