diff --git a/.changeset/purple-sloths-pump.md b/.changeset/purple-sloths-pump.md new file mode 100644 index 0000000000..6b6e9fc5a7 --- /dev/null +++ b/.changeset/purple-sloths-pump.md @@ -0,0 +1,6 @@ +--- +"@medusajs/client-types": patch +"@medusajs/medusa": patch +--- + +fix(medusa): Add missing query params to draft order list endpoint 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 77d7c84a1e..0a92509245 100644 --- a/packages/admin-next/dashboard/public/locales/en-US/translation.json +++ b/packages/admin-next/dashboard/public/locales/en-US/translation.json @@ -255,7 +255,12 @@ } }, "draftOrders": { - "domain": "Draft Orders" + "domain": "Draft Orders", + "deleteWarning": "You are about to delete the draft order {{id}}. This action cannot be undone.", + "status": { + "open": "Open", + "completed": "Completed" + } }, "discounts": { "domain": "Discounts", diff --git a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx index 557deb37e5..0ce7cbf455 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx @@ -109,11 +109,13 @@ const router = createBrowserRouter([ children: [ { index: true, - lazy: () => import("../../routes/draft-orders/list"), + lazy: () => + import("../../routes/draft-orders/draft-order-list"), }, { path: ":id", - lazy: () => import("../../routes/draft-orders/details"), + lazy: () => + import("../../routes/draft-orders/draft-order-detail"), }, ], }, diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/details/details.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/details/details.tsx deleted file mode 100644 index b8524e12f6..0000000000 --- a/packages/admin-next/dashboard/src/routes/draft-orders/details/details.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const DraftOrderDetails = () => { - return
Draft Order Details
; -}; diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/details/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/details/index.ts deleted file mode 100644 index 161c177bad..0000000000 --- a/packages/admin-next/dashboard/src/routes/draft-orders/details/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DraftOrderDetails as Component } from "./details"; diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/details.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/details.tsx new file mode 100644 index 0000000000..c674982706 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/details.tsx @@ -0,0 +1,3 @@ +export const DraftOrderDetails = () => { + return
Draft Order Details
+} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/index.ts new file mode 100644 index 0000000000..c593eef663 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/index.ts @@ -0,0 +1 @@ +export { DraftOrderDetails as Component } from "./details" diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/draft-order-list-table.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/draft-order-list-table.tsx new file mode 100644 index 0000000000..84a97da27e --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/draft-order-list-table.tsx @@ -0,0 +1,69 @@ +import { Button, Container, Heading } from "@medusajs/ui" +import { useAdminDraftOrders } from "medusa-react" +import { useTranslation } from "react-i18next" +import { Link } from "react-router-dom" +import { DataTable } from "../../../../../components/table/data-table" +import { useDataTable } from "../../../../../hooks/use-data-table" +import { useDraftOrderTableColumns } from "./use-draft-order-table-columns" +import { useDraftOrderTableFilters } from "./use-draft-order-table-filters" +import { useDraftOrderTableQuery } from "./use-draft-order-table-query" + +const PAGE_SIZE = 20 + +export const DraftOrderListTable = () => { + const { t } = useTranslation() + + const { searchParams, raw } = useDraftOrderTableQuery({ + pageSize: PAGE_SIZE, + }) + const { draft_orders, count, isLoading, isError, error } = + useAdminDraftOrders( + { + ...searchParams, + expand: "cart,cart.customer", + }, + { + keepPreviousData: true, + } + ) + + const columns = useDraftOrderTableColumns() + const filters = useDraftOrderTableFilters() + + const { table } = useDataTable({ + data: draft_orders || [], + columns, + count, + enablePagination: true, + getRowId: (row) => row.id, + pageSize: PAGE_SIZE, + }) + + if (isError) { + throw error + } + + return ( + +
+ {t("draftOrders.domain")} + +
+ row.original.id} + orderBy={["status", "created_at", "updated_at"]} + queryObject={raw} + /> +
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/draft-order-table-actions.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/draft-order-table-actions.tsx new file mode 100644 index 0000000000..bcb864a8bf --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/draft-order-table-actions.tsx @@ -0,0 +1,59 @@ +import { PencilSquare, Trash } from "@medusajs/icons" +import { DraftOrder } from "@medusajs/medusa" +import { usePrompt } from "@medusajs/ui" +import { useAdminDeleteDraftOrder } from "medusa-react" +import { useTranslation } from "react-i18next" +import { ActionMenu } from "../../../../../components/common/action-menu" + +export const DraftOrderTableActions = ({ + draftOrder, +}: { + draftOrder: DraftOrder +}) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useAdminDeleteDraftOrder(draftOrder.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("draftOrders.deleteWarning", { + id: `#${draftOrder.display_id}`, + }), + confirmText: t("actions.delete"), + cancelText: t("actions.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync() + } + + return ( + , + }, + ], + }, + { + actions: [ + { + label: t("actions.delete"), + onClick: handleDelete, + icon: , + }, + ], + }, + ]} + /> + ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/index.ts new file mode 100644 index 0000000000..8e17253904 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/index.ts @@ -0,0 +1 @@ +export * from "./draft-order-list-table" diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-columns.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-columns.tsx new file mode 100644 index 0000000000..b05fb04592 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-columns.tsx @@ -0,0 +1,81 @@ +import { DraftOrder } from "@medusajs/medusa" +import { createColumnHelper } from "@tanstack/react-table" +import { useMemo } from "react" +import { useTranslation } from "react-i18next" +import { DateCell } from "../../../../../components/table/table-cells/common/date-cell" +import { StatusCell } from "../../../../../components/table/table-cells/common/status-cell" +import { + CustomerCell, + CustomerHeader, +} from "../../../../../components/table/table-cells/order/customer-cell" +import { DraftOrderTableActions } from "./draft-order-table-actions" + +const columnHelper = createColumnHelper() + +export const useDraftOrderTableColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.accessor("display_id", { + header: t("fields.id"), + cell: ({ getValue }) => ( +
+ #{getValue()} +
+ ), + }), + columnHelper.accessor("status", { + header: t("fields.status"), + cell: ({ getValue }) => { + const status = getValue() + + const { color, label } = { + open: { color: "orange", label: t("draftOrders.status.open") }, + completed: { + color: "green", + label: t("draftOrders.status.completed"), + }, + }[status] as { color: "green" | "orange"; label: string } + + return {label} + }, + }), + columnHelper.accessor("order", { + header: t("fields.order"), + cell: ({ getValue }) => { + const displayId = getValue()?.display_id + + return ( +
+ + {displayId ? `#${displayId}` : "-"} + +
+ ) + }, + }), + columnHelper.accessor("cart.customer", { + header: () => , + cell: ({ getValue }) => { + const customer = getValue() + + return + }, + }), + columnHelper.accessor("created_at", { + header: t("fields.createdAt"), + cell: ({ getValue }) => { + const date = getValue() + + return + }, + }), + columnHelper.display({ + id: "actions", + cell: ({ row }) => , + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-filters.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-filters.tsx new file mode 100644 index 0000000000..55bfa2d8ab --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-filters.tsx @@ -0,0 +1,36 @@ +import { useTranslation } from "react-i18next" +import { Filter } from "../../../../../components/table/data-table" + +export const useDraftOrderTableFilters = () => { + const { t } = useTranslation() + + const filters: Filter[] = [ + { + type: "select", + multiple: true, + key: "status", + label: t("fields.status"), + options: [ + { + label: t("draftOrders.status.open"), + value: "open", + }, + { + label: t("draftOrders.status.completed"), + value: "completed", + }, + ], + }, + ] + + const dateFilters: 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", + })) + + return [...filters, ...dateFilters] +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-query.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-query.tsx new file mode 100644 index 0000000000..8b6f16e24a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/components/draft-order-list-table/use-draft-order-table-query.tsx @@ -0,0 +1,31 @@ +import { AdminGetDraftOrdersParams } from "@medusajs/medusa" +import { useQueryParams } from "../../../../../hooks/use-query-params" + +export const useDraftOrderTableQuery = ({ + pageSize = 20, + prefix, +}: { + pageSize?: number + prefix?: string +}) => { + const raw = useQueryParams( + ["offset", "q", "order", "status", "created_at", "updated_at"], + prefix + ) + + const { status, offset, created_at, updated_at, ...rest } = raw + + const searchParams: AdminGetDraftOrdersParams = { + limit: pageSize, + offset: offset ? Number(offset) : 0, + status: status ? (status.split(",") as ["open" | "completed"]) : undefined, + created_at: created_at ? JSON.parse(created_at) : undefined, + updated_at: updated_at ? JSON.parse(updated_at) : undefined, + ...rest, + } + + return { + searchParams, + raw, + } +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/draft-order-list.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/draft-order-list.tsx new file mode 100644 index 0000000000..953d9aa729 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/draft-order-list.tsx @@ -0,0 +1,9 @@ +import { DraftOrderListTable } from "./components/draft-order-list-table" + +export const DraftOrderList = () => { + return ( +
+ +
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/index.ts new file mode 100644 index 0000000000..4e80904b5c --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-list/index.ts @@ -0,0 +1 @@ +export { DraftOrderList as Component } from "./draft-order-list" diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/list/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/list/index.ts deleted file mode 100644 index faac180186..0000000000 --- a/packages/admin-next/dashboard/src/routes/draft-orders/list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DraftOrderList as Component } from "./list"; diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/list/list.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/list/list.tsx deleted file mode 100644 index bb62d485b5..0000000000 --- a/packages/admin-next/dashboard/src/routes/draft-orders/list/list.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Container, Heading } from "@medusajs/ui"; - -export const DraftOrderList = () => { - return ( - - Draft Orders - - ); -}; diff --git a/packages/generated/client-types/src/lib/models/AdminGetDraftOrdersParams.ts b/packages/generated/client-types/src/lib/models/AdminGetDraftOrdersParams.ts index 81ef0dadca..4adee26a21 100644 --- a/packages/generated/client-types/src/lib/models/AdminGetDraftOrdersParams.ts +++ b/packages/generated/client-types/src/lib/models/AdminGetDraftOrdersParams.ts @@ -16,4 +16,62 @@ export interface AdminGetDraftOrdersParams { * a term to search draft orders' display IDs and emails in the draft order's cart */ q?: string + /** + * Field to sort retrieved draft orders by. + */ + order?: string + /** + * A comma-separated list of fields to expand. + */ + expand?: string + /** + * A comma-separated list of fields to include in the response. + */ + fields?: 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 + } + /** + * Filter by status + */ + status?: Array<"open" | "completed"> } diff --git a/packages/medusa/src/api/routes/admin/draft-orders/index.ts b/packages/medusa/src/api/routes/admin/draft-orders/index.ts index ebef7957fd..b729753cfc 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/index.ts +++ b/packages/medusa/src/api/routes/admin/draft-orders/index.ts @@ -1,14 +1,23 @@ import { Router } from "express" import { Cart, DraftOrder, Order } from "../../../.." import { DeleteResponse, PaginatedResponse } from "../../../../types/common" -import middlewares from "../../../middlewares" +import middlewares, { transformQuery } from "../../../middlewares" +import { AdminGetDraftOrdersParams } from "./list-draft-orders" const route = Router() export default (app) => { app.use("/draft-orders", route) - route.get("/", middlewares.wrap(require("./list-draft-orders").default)) + route.get( + "/", + transformQuery(AdminGetDraftOrdersParams, { + defaultFields: defaultAdminDraftOrdersFields, + defaultRelations: defaultAdminDraftOrdersRelations, + isList: true, + }), + middlewares.wrap(require("./list-draft-orders").default) + ) route.get("/:id", middlewares.wrap(require("./get-draft-order").default)) diff --git a/packages/medusa/src/api/routes/admin/draft-orders/list-draft-orders.ts b/packages/medusa/src/api/routes/admin/draft-orders/list-draft-orders.ts index 15dd5dee93..c92c4b38ab 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/list-draft-orders.ts +++ b/packages/medusa/src/api/routes/admin/draft-orders/list-draft-orders.ts @@ -1,15 +1,19 @@ -import { IsNumber, IsOptional, IsString } from "class-validator" -import { - defaultAdminDraftOrdersFields, - defaultAdminDraftOrdersRelations, -} from "." - -import { DraftOrder } from "../../../../models" -import { DraftOrderListSelector } from "../../../../types/draft-orders" -import { DraftOrderService } from "../../../../services" -import { FindConfig } from "../../../../types/common" import { Type } from "class-transformer" -import { validator } from "../../../../utils/validator" +import { + IsArray, + IsEnum, + IsNumber, + IsOptional, + IsString, + ValidateNested, +} from "class-validator" +import { DraftOrderStatus } from "../../../../models" +import { DraftOrderService } from "../../../../services" +import { + DateComparisonOperator, + extendedFindParamsMixin, +} from "../../../../types/common" +import { DraftOrderStatusValue } from "../../../../types/draft-orders" /** * @oas [get] /admin/draft-orders @@ -21,6 +25,63 @@ import { validator } from "../../../../utils/validator" * - (query) offset=0 {number} The number of draft orders to skip when retrieving the draft orders. * - (query) limit=50 {number} Limit the number of draft orders returned. * - (query) q {string} a term to search draft orders' display IDs and emails in the draft order's cart + * - (query) order {string} Field to sort retrieved draft orders by. + * - (query) expand {string} A comma-separated list of fields to expand. + * - (query) fields {string} A comma-separated list of fields to include in the response. + * - 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 + * - in: query + * name: status + * style: form + * explode: false + * description: Filter by status + * schema: + * type: array + * items: + * type: string + * enum: [open, completed] * x-codegen: * method: list * queryParams: AdminGetDraftOrdersParams @@ -98,39 +159,27 @@ export default async (req, res) => { const draftOrderService: DraftOrderService = req.scope.resolve("draftOrderService") - const validated = await validator(AdminGetDraftOrdersParams, req.query) - - const selector: DraftOrderListSelector = {} - - if (validated.q) { - selector.q = validated.q - } - - const listConfig: FindConfig = { - select: defaultAdminDraftOrdersFields, - relations: defaultAdminDraftOrdersRelations, - skip: validated.offset ?? 0, - take: validated.limit ?? 50, - order: { created_at: "DESC" }, - } + const { skip, take } = req.listConfig const [draftOrders, count] = await draftOrderService.listAndCount( - selector, - listConfig + req.filterableFields, + req.listConfig ) res.json({ draft_orders: draftOrders, count, - offset: validated.offset, - limit: validated.limit, + offset: skip, + limit: take, }) } /** * Parameters used to filter and configure the pagination of the retrieved draft orders. */ -export class AdminGetDraftOrdersParams { +export class AdminGetDraftOrdersParams extends extendedFindParamsMixin({ + limit: 50, +}) { /** * Search term to search draft orders by their display IDs and emails. */ @@ -139,18 +188,41 @@ export class AdminGetDraftOrdersParams { q?: string /** - * {@inheritDoc FindPaginationParams.limit} + * {@inheritDoc FindParams.expand} */ - @IsNumber() @IsOptional() - @Type(() => Number) - limit?: number = 50 + @IsString() + expand?: string /** - * {@inheritDoc FindPaginationParams.offset} + * {@inheritDoc FindPaginationParams.limit} + * @defaultValue 50 */ - @IsNumber() @IsOptional() - @Type(() => Number) - offset?: number = 0 + @IsNumber() + fields?: string + + /** + * Statuses to filter draft orders by. + */ + @IsArray() + @IsEnum(DraftOrderStatus, { each: true }) + @IsOptional() + status?: DraftOrderStatusValue[] + + /** + * Date filters to apply on the draft orders' `created_at` date. + */ + @IsOptional() + @ValidateNested() + @Type(() => DateComparisonOperator) + created_at?: DateComparisonOperator + + /** + * Date filters to apply on the draft orders' `updated_at` date. + */ + @IsOptional() + @ValidateNested() + @Type(() => DateComparisonOperator) + updated_at?: DateComparisonOperator } diff --git a/packages/medusa/src/types/draft-orders.ts b/packages/medusa/src/types/draft-orders.ts index 533ff102d1..d68340d144 100644 --- a/packages/medusa/src/types/draft-orders.ts +++ b/packages/medusa/src/types/draft-orders.ts @@ -1,5 +1,7 @@ import { AddressPayload } from "./common" +export type DraftOrderStatusValue = "open" | "completed" + export type DraftOrderListSelector = { q?: string } export type DraftOrderCreateProps = {