From 608c10383a1a86068f0648d5aa641cbf11ffd323 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Frane=20Poli=C4=87?=
<16856471+fPolic@users.noreply.github.com>
Date: Tue, 27 Feb 2024 12:33:19 +0100
Subject: [PATCH] feat(admin-next) discounts list page (#6490)
---
.changeset/polite-papayas-exercise.md | 5 +
.../public/locales/en-US/translation.json | 15 +-
.../discount/code-cell/code-cell.tsx | 28 ++++
.../table-cells/discount/code-cell/index.ts | 1 +
.../description-cell/description-cell.tsx | 25 +++
.../discount/description-cell/index.ts | 1 +
.../discount/redemption-cell/index.ts | 1 +
.../redemption-cell/redemption-cell.tsx | 23 +++
.../table-cells/discount/status-cell/index.ts | 1 +
.../discount/status-cell/status-cell.tsx | 72 +++++++++
.../table-cells/discount/value-cell/index.ts | 1 +
.../discount/value-cell/value-cell.tsx | 35 +++++
.../columns/use-discount-table-columns.tsx | 65 ++++++++
.../filters/use-discount-table-filters.tsx | 51 ++++++
.../table/query/use-discount-table-query.tsx | 45 ++++++
.../discount-list-table.tsx | 145 ++++++++++++++++++
.../components/discount-list-table/index.ts | 1 +
.../src/routes/discounts/list/index.ts | 3 +-
.../src/routes/discounts/list/list.tsx | 21 ++-
.../src/routes/discounts/list/loader.ts | 24 +++
.../src/lib/models/AdminGetDiscountsParams.ts | 46 ++++++
.../routes/admin/discounts/list-discounts.ts | 73 ++++++++-
packages/medusa/src/types/discount.ts | 11 ++
23 files changed, 683 insertions(+), 10 deletions(-)
create mode 100644 .changeset/polite-papayas-exercise.md
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/code-cell.tsx
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/index.ts
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/description-cell.tsx
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/index.ts
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/index.ts
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/redemption-cell.tsx
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/index.ts
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/status-cell.tsx
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/index.ts
create mode 100644 packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/value-cell.tsx
create mode 100644 packages/admin-next/dashboard/src/hooks/table/columns/use-discount-table-columns.tsx
create mode 100644 packages/admin-next/dashboard/src/hooks/table/filters/use-discount-table-filters.tsx
create mode 100644 packages/admin-next/dashboard/src/hooks/table/query/use-discount-table-query.tsx
create mode 100644 packages/admin-next/dashboard/src/routes/discounts/list/components/discount-list-table/discount-list-table.tsx
create mode 100644 packages/admin-next/dashboard/src/routes/discounts/list/components/discount-list-table/index.ts
create mode 100644 packages/admin-next/dashboard/src/routes/discounts/list/loader.ts
diff --git a/.changeset/polite-papayas-exercise.md b/.changeset/polite-papayas-exercise.md
new file mode 100644
index 0000000000..571fc9ff48
--- /dev/null
+++ b/.changeset/polite-papayas-exercise.md
@@ -0,0 +1,5 @@
+---
+"@medusajs/medusa": patch
+---
+
+fix(medusa): add `order` param to discounts list
diff --git a/packages/admin-next/dashboard/public/locales/en-US/translation.json b/packages/admin-next/dashboard/public/locales/en-US/translation.json
index 02892e6db7..f7875cbde2 100644
--- a/packages/admin-next/dashboard/public/locales/en-US/translation.json
+++ b/packages/admin-next/dashboard/public/locales/en-US/translation.json
@@ -187,7 +187,14 @@
"domain": "Draft Orders"
},
"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": {
"domain": "Pricing"
@@ -355,6 +362,11 @@
"sales_channels": "Sales Channels",
"status": "Status",
"code": "Code",
+ "value": "Value",
+ "disabled": "Disabled",
+ "dynamic": "Dynamic",
+ "normal": "Normal",
+ "totalRedemptions": "Total Redemptions",
"countries": "Countries",
"paymentProviders": "Payment Providers",
"fulfillmentProviders": "Fulfillment Providers",
@@ -392,6 +404,7 @@
"items": "Items",
"salesChannel": "Sales Channel",
"region": "Region",
+ "discount": "Discount",
"role": "Role",
"sent": "Sent",
"salesChannels": "Sales Channels",
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/code-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/code-cell.tsx
new file mode 100644
index 0000000000..b6d0cf4994
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/code-cell.tsx
@@ -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 (
+
+ {/*// TODO: border color inversion*/}
+
+ {discount.code}
+
+
+ )
+}
+
+export const CodeHeader = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ {t("fields.description")}
+
+ )
+}
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/index.ts
new file mode 100644
index 0000000000..a2eb7174de
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/index.ts
@@ -0,0 +1 @@
+export * from "./code-cell"
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/description-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/description-cell.tsx
new file mode 100644
index 0000000000..b625626782
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/description-cell.tsx
@@ -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 (
+
+ {discount.rule.description}
+
+ )
+}
+
+export const DescriptionHeader = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ {t("fields.description")}
+
+ )
+}
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/index.ts
new file mode 100644
index 0000000000..83760e2da6
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/index.ts
@@ -0,0 +1 @@
+export * from "./description-cell"
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/index.ts
new file mode 100644
index 0000000000..faac33e975
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/index.ts
@@ -0,0 +1 @@
+export * from "./redemption-cell.tsx"
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/redemption-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/redemption-cell.tsx
new file mode 100644
index 0000000000..00c76fd738
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/redemption-cell.tsx
@@ -0,0 +1,23 @@
+import { useTranslation } from "react-i18next"
+
+type DiscountCellProps = {
+ redemptions: number
+}
+
+export const RedemptionCell = ({ redemptions }: DiscountCellProps) => {
+ return (
+
+ {redemptions}
+
+ )
+}
+
+export const RedemptionHeader = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ {t("fields.totalRedemptions")}
+
+ )
+}
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/index.ts
new file mode 100644
index 0000000000..8cc5d6026b
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/index.ts
@@ -0,0 +1 @@
+export * from "./status-cell"
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/status-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/status-cell.tsx
new file mode 100644
index 0000000000..61a0362090
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/status-cell.tsx
@@ -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 {text}
+}
+
+export const StatusHeader = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ {t("fields.status")}
+
+ )
+}
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/index.ts
new file mode 100644
index 0000000000..91c37e81b2
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/index.ts
@@ -0,0 +1 @@
+export * from "./value-cell.tsx"
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/value-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/value-cell.tsx
new file mode 100644
index 0000000000..6ce4b0c86e
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/value-cell.tsx
@@ -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 (
+
+ {isFreeShipping && Free shipping }
+ {isPercentage && {rule.value}% }
+ {isFixed && (
+
+ )}
+
+ )
+}
+
+export const ValueHeader = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ {t("fields.value")}
+
+ )
+}
diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/use-discount-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-discount-table-columns.tsx
new file mode 100644
index 0000000000..3c2ed34960
--- /dev/null
+++ b/packages/admin-next/dashboard/src/hooks/table/columns/use-discount-table-columns.tsx
@@ -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()
+
+export const useDiscountTableColumns = () => {
+ return useMemo(
+ () => [
+ columnHelper.display({
+ id: "discount",
+ header: () => ,
+ cell: ({ row }) => ,
+ }),
+ columnHelper.accessor("rule.description", {
+ header: () => ,
+ cell: ({ row }) => ,
+ }),
+
+ columnHelper.accessor("rule.value", {
+ header: () => ,
+ cell: ({ row }) => (
+
+ ),
+ }),
+ columnHelper.display({
+ id: "status",
+ header: () => ,
+ cell: ({ row }) => ,
+ }),
+ columnHelper.accessor("usage_count", {
+ header: () => ,
+ cell: ({ row }) => (
+
+ ),
+ }),
+ ],
+ []
+ ) as ColumnDef[]
+}
diff --git a/packages/admin-next/dashboard/src/hooks/table/filters/use-discount-table-filters.tsx b/packages/admin-next/dashboard/src/hooks/table/filters/use-discount-table-filters.tsx
new file mode 100644
index 0000000000..8158b0a3d8
--- /dev/null
+++ b/packages/admin-next/dashboard/src/hooks/table/filters/use-discount-table-filters.tsx
@@ -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
+}
diff --git a/packages/admin-next/dashboard/src/hooks/table/query/use-discount-table-query.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-discount-table-query.tsx
new file mode 100644
index 0000000000..a3f9f0e6fb
--- /dev/null
+++ b/packages/admin-next/dashboard/src/hooks/table/query/use-discount-table-query.tsx
@@ -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,
+ }
+}
diff --git a/packages/admin-next/dashboard/src/routes/discounts/list/components/discount-list-table/discount-list-table.tsx b/packages/admin-next/dashboard/src/routes/discounts/list/components/discount-list-table/discount-list-table.tsx
new file mode 100644
index 0000000000..71cd438564
--- /dev/null
+++ b/packages/admin-next/dashboard/src/routes/discounts/list/components/discount-list-table/discount-list-table.tsx
@@ -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>
+ >
+
+ 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 (
+
+
+ {t("discounts.domain")}
+
+ {t("actions.create")}
+
+
+ `${row.original.id}`}
+ orderBy={["code", "created_at", "updated_at"]}
+ />
+
+
+ )
+}
+
+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 (
+ ,
+ label: t("actions.edit"),
+ to: `/discounts/${discount.id}/edit`,
+ },
+ ],
+ },
+ {
+ actions: [
+ {
+ icon: ,
+ label: t("actions.delete"),
+ onClick: handleDelete,
+ },
+ ],
+ },
+ ]}
+ />
+ )
+}
+
+const columnHelper = createColumnHelper()
+
+const useColumns = () => {
+ const base = useDiscountTableColumns()
+
+ return useMemo(
+ () => [
+ ...base,
+ columnHelper.display({
+ id: "actions",
+ cell: ({ row }) => {
+ return
+ },
+ }),
+ ],
+ [base]
+ )
+}
diff --git a/packages/admin-next/dashboard/src/routes/discounts/list/components/discount-list-table/index.ts b/packages/admin-next/dashboard/src/routes/discounts/list/components/discount-list-table/index.ts
new file mode 100644
index 0000000000..caa2b8e5d3
--- /dev/null
+++ b/packages/admin-next/dashboard/src/routes/discounts/list/components/discount-list-table/index.ts
@@ -0,0 +1 @@
+export * from "./discount-list-table.tsx"
diff --git a/packages/admin-next/dashboard/src/routes/discounts/list/index.ts b/packages/admin-next/dashboard/src/routes/discounts/list/index.ts
index 9ede617802..4a47518db9 100644
--- a/packages/admin-next/dashboard/src/routes/discounts/list/index.ts
+++ b/packages/admin-next/dashboard/src/routes/discounts/list/index.ts
@@ -1 +1,2 @@
-export { DiscountsList as Component } from "./list";
+export { discountsLoader } from "./loader"
+export { DiscountsList as Component } from "./list"
diff --git a/packages/admin-next/dashboard/src/routes/discounts/list/list.tsx b/packages/admin-next/dashboard/src/routes/discounts/list/list.tsx
index 5be301f0c6..d543729208 100644
--- a/packages/admin-next/dashboard/src/routes/discounts/list/list.tsx
+++ b/packages/admin-next/dashboard/src/routes/discounts/list/list.tsx
@@ -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 = () => {
return (
-
-
- Discounts
-
+
+ {before.widgets.map((w, i) => (
+
+ ))}
+
+ {after.widgets.map((w, i) => (
+
+ ))}
- );
-};
+ )
+}
diff --git a/packages/admin-next/dashboard/src/routes/discounts/list/loader.ts b/packages/admin-next/dashboard/src/routes/discounts/list/loader.ts
new file mode 100644
index 0000000000..026655de47
--- /dev/null
+++ b/packages/admin-next/dashboard/src/routes/discounts/list/loader.ts
@@ -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
>(
+ query.queryKey
+ ) ?? (await client.fetchQuery(query))
+ )
+ }
+}
diff --git a/packages/generated/client-types/src/lib/models/AdminGetDiscountsParams.ts b/packages/generated/client-types/src/lib/models/AdminGetDiscountsParams.ts
index baf5329143..428a128704 100644
--- a/packages/generated/client-types/src/lib/models/AdminGetDiscountsParams.ts
+++ b/packages/generated/client-types/src/lib/models/AdminGetDiscountsParams.ts
@@ -41,4 +41,50 @@ export interface AdminGetDiscountsParams {
* Comma-separated relations that should be expanded in each returned discount.
*/
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
+ }
}
diff --git a/packages/medusa/src/api/routes/admin/discounts/list-discounts.ts b/packages/medusa/src/api/routes/admin/discounts/list-discounts.ts
index bce231e7c2..1a786920dd 100644
--- a/packages/medusa/src/api/routes/admin/discounts/list-discounts.ts
+++ b/packages/medusa/src/api/routes/admin/discounts/list-discounts.ts
@@ -7,7 +7,10 @@ import {
import { Transform, Type } from "class-transformer"
import { AdminGetDiscountsDiscountRuleParams } from "../../../../types/discount"
-import { extendedFindParamsMixin } from "../../../../types/common"
+import {
+ DateComparisonOperator,
+ extendedFindParamsMixin,
+} from "../../../../types/common"
import { Request, Response } from "express"
import { DiscountService } from "../../../../services"
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) 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) 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:
* method: list
* queryParams: AdminGetDiscountsParams
@@ -167,4 +215,27 @@ export class AdminGetDiscountsParams extends extendedFindParamsMixin({
@IsOptional()
@Transform(({ value }) => optionalBooleanMapper.get(value))
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
}
diff --git a/packages/medusa/src/types/discount.ts b/packages/medusa/src/types/discount.ts
index a22f1bd89c..d54a7d07b4 100644
--- a/packages/medusa/src/types/discount.ts
+++ b/packages/medusa/src/types/discount.ts
@@ -18,6 +18,7 @@ import {
import { optionalBooleanMapper } from "../utils/validators/is-boolean"
import { IsType } from "../utils/validators/is-type"
import { ExactlyOne } from "./validators/exactly-one"
+import { DateComparisonOperator } from "./common"
export type QuerySelector = {
q?: string
@@ -46,6 +47,16 @@ export class FilterableDiscountProps {
@IsOptional()
@Type(() => AdminGetDiscountsDiscountRuleParams)
rule?: AdminGetDiscountsDiscountRuleParams
+
+ @IsOptional()
+ @ValidateNested()
+ @Type(() => DateComparisonOperator)
+ created_at?: DateComparisonOperator
+
+ @IsOptional()
+ @ValidateNested()
+ @Type(() => DateComparisonOperator)
+ updated_at?: DateComparisonOperator
}
/**