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"
|
||||
},
|
||||
"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",
|
||||
|
||||
+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 = () => {
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<Heading>Discounts</Heading>
|
||||
</Container>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => (
|
||||
<w.Component key={i} />
|
||||
))}
|
||||
<DiscountListTable />
|
||||
{after.widgets.map((w, i) => (
|
||||
<w.Component key={i} />
|
||||
))}
|
||||
</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.
|
||||
*/
|
||||
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 { 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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user