feat(dashboard,medusa,ui): Manual gift cards + cleanup (#6380)
**Dashboard** - Adds different views for managing manual/custom gift cards (not associated with a product gift card). - Cleans up several table implementations to use new DataTable component. - Minor cleanup of translation file. **Medusa** - Adds missing query params for list endpoints in the following admin domains: /customers, /customer-groups, /collections, and /gift-cards. **UI** - Adds new sizes for Badge component. **Note for review** Since this PR contains updates to the translation keys, it touches a lot of files. For the review the parts that are relevant are: the /gift-cards domain of admin, the table overview of collections, customers, and customer groups. And the changes to the list endpoints in the core.
This commit is contained in:
committed by
GitHub
parent
bc2a63782b
commit
d37ff8024d
@@ -0,0 +1,51 @@
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
|
||||
import {
|
||||
EmailCell,
|
||||
EmailHeader,
|
||||
} from "../../../components/table/table-cells/common/email-cell"
|
||||
import {
|
||||
NameCell,
|
||||
NameHeader,
|
||||
} from "../../../components/table/table-cells/common/name-cell"
|
||||
import {
|
||||
AccountCell,
|
||||
AccountHeader,
|
||||
} from "../../../components/table/table-cells/customer/account-cell/account-cell"
|
||||
import {
|
||||
FirstSeenCell,
|
||||
FirstSeenHeader,
|
||||
} from "../../../components/table/table-cells/customer/first-seen-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<Customer>()
|
||||
|
||||
export const useCustomerTableColumns = () => {
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("email", {
|
||||
header: () => <EmailHeader />,
|
||||
cell: ({ getValue }) => <EmailCell email={getValue()} />,
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "name",
|
||||
header: () => <NameHeader />,
|
||||
cell: ({
|
||||
row: {
|
||||
original: { first_name, last_name },
|
||||
},
|
||||
}) => <NameCell firstName={first_name} lastName={last_name} />,
|
||||
}),
|
||||
columnHelper.accessor("has_account", {
|
||||
header: () => <AccountHeader />,
|
||||
cell: ({ getValue }) => <AccountCell hasAccount={getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("created_at", {
|
||||
header: () => <FirstSeenHeader />,
|
||||
cell: ({ getValue }) => <FirstSeenCell createdAt={getValue()} />,
|
||||
}),
|
||||
],
|
||||
[]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
|
||||
import {
|
||||
CollectionCell,
|
||||
CollectionHeader,
|
||||
} from "../../../components/table/table-cells/product/collection-cell/collection-cell"
|
||||
import {
|
||||
ProductCell,
|
||||
ProductHeader,
|
||||
} from "../../../components/table/table-cells/product/product-cell"
|
||||
import {
|
||||
ProductStatusCell,
|
||||
ProductStatusHeader,
|
||||
} from "../../../components/table/table-cells/product/product-status-cell"
|
||||
import {
|
||||
SalesChannelHeader,
|
||||
SalesChannelsCell,
|
||||
} from "../../../components/table/table-cells/product/sales-channels-cell"
|
||||
import {
|
||||
VariantCell,
|
||||
VariantHeader,
|
||||
} from "../../../components/table/table-cells/product/variant-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<Product>()
|
||||
|
||||
export const useProductTableColumns = () => {
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "product",
|
||||
header: () => <ProductHeader />,
|
||||
cell: ({ row }) => <ProductCell product={row.original} />,
|
||||
}),
|
||||
columnHelper.accessor("collection", {
|
||||
header: () => <CollectionHeader />,
|
||||
cell: ({ row }) => (
|
||||
<CollectionCell collection={row.original.collection} />
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("sales_channels", {
|
||||
header: () => <SalesChannelHeader />,
|
||||
cell: ({ row }) => (
|
||||
<SalesChannelsCell salesChannels={row.original.sales_channels} />
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("variants", {
|
||||
header: () => <VariantHeader />,
|
||||
cell: ({ row }) => <VariantCell variants={row.original.variants} />,
|
||||
}),
|
||||
columnHelper.accessor("status", {
|
||||
header: () => <ProductStatusHeader />,
|
||||
cell: ({ row }) => <ProductStatusCell status={row.original.status} />,
|
||||
}),
|
||||
],
|
||||
[]
|
||||
) as ColumnDef<Product>[]
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useAdminCustomerGroups } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../components/table/data-table"
|
||||
|
||||
const excludeableFields = ["groups"] as const
|
||||
|
||||
export const useCustomerTableFilters = (
|
||||
exclude?: (typeof excludeableFields)[number][]
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const isGroupsExcluded = exclude?.includes("groups")
|
||||
|
||||
const { customer_groups } = useAdminCustomerGroups(
|
||||
{
|
||||
limit: 1000,
|
||||
expand: "",
|
||||
},
|
||||
{
|
||||
enabled: !isGroupsExcluded,
|
||||
}
|
||||
)
|
||||
|
||||
let filters: Filter[] = []
|
||||
|
||||
if (customer_groups && !isGroupsExcluded) {
|
||||
const customerGroupFilter: Filter = {
|
||||
key: "groups",
|
||||
label: t("customers.groups"),
|
||||
type: "select",
|
||||
multiple: true,
|
||||
options: customer_groups.map((s) => ({
|
||||
label: s.name,
|
||||
value: s.id,
|
||||
})),
|
||||
}
|
||||
|
||||
filters = [...filters, customerGroupFilter]
|
||||
}
|
||||
|
||||
const hasAccountFilter: Filter = {
|
||||
key: "has_account",
|
||||
label: t("fields.account"),
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: t("customers.registered"),
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
label: t("customers.guest"),
|
||||
value: "false",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
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",
|
||||
}))
|
||||
|
||||
filters = [...filters, hasAccountFilter, ...dateFilters]
|
||||
|
||||
return filters
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import {
|
||||
useAdminCollections,
|
||||
useAdminProductCategories,
|
||||
useAdminProductTags,
|
||||
useAdminProductTypes,
|
||||
useAdminSalesChannels,
|
||||
} from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../components/table/data-table"
|
||||
|
||||
const excludeableFields = ["sales_channel_id", "collections"] as const
|
||||
|
||||
export const useProductTableFilters = (
|
||||
exclude?: (typeof excludeableFields)[number][]
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product_types } = useAdminProductTypes({
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
})
|
||||
|
||||
const { product_tags } = useAdminProductTags({
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
})
|
||||
|
||||
const isSalesChannelExcluded = exclude?.includes("sales_channel_id")
|
||||
|
||||
const { sales_channels } = useAdminSalesChannels(
|
||||
{
|
||||
limit: 1000,
|
||||
fields: "id,name",
|
||||
expand: "",
|
||||
},
|
||||
{
|
||||
enabled: !isSalesChannelExcluded,
|
||||
}
|
||||
)
|
||||
|
||||
const { product_categories } = useAdminProductCategories({
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
fields: "id,name",
|
||||
expand: "",
|
||||
})
|
||||
|
||||
const isCollectionExcluded = exclude?.includes("collections")
|
||||
|
||||
const { collections } = useAdminCollections(
|
||||
{
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
enabled: !isCollectionExcluded,
|
||||
}
|
||||
)
|
||||
|
||||
let filters: Filter[] = []
|
||||
|
||||
if (product_types) {
|
||||
const typeFilter: Filter = {
|
||||
key: "type_id",
|
||||
label: t("fields.type"),
|
||||
type: "select",
|
||||
multiple: true,
|
||||
options: product_types.map((t) => ({
|
||||
label: t.value,
|
||||
value: t.id,
|
||||
})),
|
||||
}
|
||||
|
||||
filters = [...filters, typeFilter]
|
||||
}
|
||||
|
||||
if (product_tags) {
|
||||
const tagFilter: Filter = {
|
||||
key: "tags",
|
||||
label: t("fields.tag"),
|
||||
type: "select",
|
||||
multiple: true,
|
||||
options: product_tags.map((t) => ({
|
||||
label: t.value,
|
||||
value: t.id,
|
||||
})),
|
||||
}
|
||||
|
||||
filters = [...filters, tagFilter]
|
||||
}
|
||||
|
||||
if (sales_channels) {
|
||||
const salesChannelFilter: Filter = {
|
||||
key: "sales_channel_id",
|
||||
label: t("fields.salesChannel"),
|
||||
type: "select",
|
||||
multiple: true,
|
||||
options: sales_channels.map((s) => ({
|
||||
label: s.name,
|
||||
value: s.id,
|
||||
})),
|
||||
}
|
||||
|
||||
filters = [...filters, salesChannelFilter]
|
||||
}
|
||||
|
||||
if (product_categories) {
|
||||
const categoryFilter: Filter = {
|
||||
key: "category_id",
|
||||
label: t("fields.category"),
|
||||
type: "select",
|
||||
multiple: true,
|
||||
options: product_categories.map((c) => ({
|
||||
label: c.name,
|
||||
value: c.id,
|
||||
})),
|
||||
}
|
||||
|
||||
filters = [...filters, categoryFilter]
|
||||
}
|
||||
|
||||
if (collections) {
|
||||
const collectionFilter: Filter = {
|
||||
key: "collection_id",
|
||||
label: t("fields.collection"),
|
||||
type: "select",
|
||||
multiple: true,
|
||||
options: collections.map((c) => ({
|
||||
label: c.title,
|
||||
value: c.id,
|
||||
})),
|
||||
}
|
||||
|
||||
filters = [...filters, collectionFilter]
|
||||
}
|
||||
|
||||
const giftCardFilter: Filter = {
|
||||
key: "is_giftcard",
|
||||
label: t("fields.giftCard"),
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: t("fields.true"),
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
label: t("fields.false"),
|
||||
value: "false",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const statusFilter: Filter = {
|
||||
key: "status",
|
||||
label: t("fields.status"),
|
||||
type: "select",
|
||||
multiple: true,
|
||||
options: [
|
||||
{
|
||||
label: t("products.productStatus.draft"),
|
||||
value: "draft",
|
||||
},
|
||||
{
|
||||
label: t("products.productStatus.proposed"),
|
||||
value: "proposed",
|
||||
},
|
||||
{
|
||||
label: t("products.productStatus.published"),
|
||||
value: "published",
|
||||
},
|
||||
{
|
||||
label: t("products.productStatus.rejected"),
|
||||
value: "rejected",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
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",
|
||||
}))
|
||||
|
||||
filters = [...filters, statusFilter, giftCardFilter, ...dateFilters]
|
||||
|
||||
return filters
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { AdminGetCustomersParams } from "@medusajs/medusa"
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UseCustomerTableQueryProps = {
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const useCustomerTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 20,
|
||||
}: UseCustomerTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
[
|
||||
"offset",
|
||||
"q",
|
||||
"has_account",
|
||||
"groups",
|
||||
"order",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
],
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, groups, has_account, q, order } = queryObject
|
||||
|
||||
const searchParams: AdminGetCustomersParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
groups: groups?.split(","),
|
||||
has_account: has_account ? has_account === "true" : undefined,
|
||||
order,
|
||||
created_at: queryObject.created_at
|
||||
? JSON.parse(queryObject.created_at)
|
||||
: undefined,
|
||||
updated_at: queryObject.updated_at
|
||||
? JSON.parse(queryObject.updated_at)
|
||||
: undefined,
|
||||
q,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,9 @@ type UseOrderTableQueryProps = {
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Enable `order` query param when staging is updated
|
||||
*/
|
||||
|
||||
export const useOrderTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 50,
|
||||
pageSize = 20,
|
||||
}: UseOrderTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
[
|
||||
@@ -24,6 +20,7 @@ export const useOrderTableQuery = ({
|
||||
"sales_channel_id",
|
||||
"payment_status",
|
||||
"fulfillment_status",
|
||||
"order",
|
||||
],
|
||||
prefix
|
||||
)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { AdminGetProductsParams } from "@medusajs/medusa"
|
||||
import { ProductStatus } from "@medusajs/types"
|
||||
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UseProductTableQueryProps = {
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const useProductTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 20,
|
||||
}: UseProductTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
[
|
||||
"offset",
|
||||
"order",
|
||||
"q",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"sales_channel_id",
|
||||
"category_id",
|
||||
"collection_id",
|
||||
"is_giftcard",
|
||||
"tags",
|
||||
"type_id",
|
||||
"status",
|
||||
],
|
||||
prefix
|
||||
)
|
||||
|
||||
const {
|
||||
offset,
|
||||
sales_channel_id,
|
||||
created_at,
|
||||
updated_at,
|
||||
category_id,
|
||||
collection_id,
|
||||
tags,
|
||||
type_id,
|
||||
is_giftcard,
|
||||
status,
|
||||
order,
|
||||
q,
|
||||
} = queryObject
|
||||
|
||||
const searchParams: AdminGetProductsParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
sales_channel_id: sales_channel_id?.split(","),
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
category_id: category_id?.split(","),
|
||||
collection_id: collection_id?.split(","),
|
||||
is_giftcard: is_giftcard ? is_giftcard === "true" : undefined,
|
||||
order: order,
|
||||
tags: tags?.split(","),
|
||||
type_id: type_id?.split(","),
|
||||
status: status?.split(",") as ProductStatus[],
|
||||
q,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user