feat(dashboard,js-sdk,types,admin-shared): Add Product Types domain (#7732)

This commit is contained in:
Kasper Fabricius Kristensen
2024-06-17 18:50:55 +02:00
committed by GitHub
parent 70a72ce2df
commit 2d8d2c4255
53 changed files with 1045 additions and 62 deletions
@@ -49,6 +49,10 @@ const useSettingRoutes = (): NavItemProps[] => {
label: t("salesChannels.domain"),
to: "/settings/sales-channels",
},
{
label: t("productTypes.domain"),
to: "/settings/product-types",
},
{
label: t("shippingProfile.domain"),
to: "/settings/shipping-profiles",
@@ -1,26 +1,34 @@
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
import { client } from "../../lib/client"
import { queryKeysFactory } from "../../lib/query-key-factory"
import { FetchError } from "@medusajs/js-sdk"
import { HttpTypes } from "@medusajs/types"
import {
QueryKey,
UseMutationOptions,
UseQueryOptions,
useMutation,
useQuery,
} from "@tanstack/react-query"
import { sdk } from "../../lib/client"
import { queryClient } from "../../lib/query-client"
import { queryKeysFactory } from "../../lib/query-key-factory"
const PRODUCT_TYPES_QUERY_KEY = "product_types" as const
const productTypesQueryKeys = queryKeysFactory(PRODUCT_TYPES_QUERY_KEY)
export const productTypesQueryKeys = queryKeysFactory(PRODUCT_TYPES_QUERY_KEY)
export const useProductType = (
id: string,
query?: Record<string, any>,
query?: HttpTypes.AdminProductTypeParams,
options?: Omit<
UseQueryOptions<
{ product_type: HttpTypes.AdminProductType },
Error,
{ product_type: HttpTypes.AdminProductType },
HttpTypes.AdminProductTypeResponse,
FetchError,
HttpTypes.AdminProductTypeResponse,
QueryKey
>,
"queryKey" | "queryFn"
>
) => {
const { data, ...rest } = useQuery({
queryFn: () => client.productTypes.retrieve(id, query),
queryFn: () => sdk.admin.productType.retrieve(id, query),
queryKey: productTypesQueryKeys.detail(id),
...options,
})
@@ -29,22 +37,84 @@ export const useProductType = (
}
export const useProductTypes = (
query?: Record<string, any>,
query?: HttpTypes.AdminProductTypeListParams,
options?: Omit<
UseQueryOptions<
{ product_types: HttpTypes.AdminProductType[] },
Error,
{ product_types: HttpTypes.AdminProductType[] },
HttpTypes.AdminProductTypeListResponse,
FetchError,
HttpTypes.AdminProductTypeListResponse,
QueryKey
>,
"queryKey" | "queryFn"
>
) => {
const { data, ...rest } = useQuery({
queryFn: () => client.productTypes.list(query),
queryFn: () => sdk.admin.productType.list(query),
queryKey: productTypesQueryKeys.list(query),
...options,
})
return { ...data, ...rest }
}
export const useCreateProductType = (
options?: UseMutationOptions<
HttpTypes.AdminProductTypeResponse,
FetchError,
HttpTypes.AdminCreateProductType
>
) => {
return useMutation({
mutationFn: (payload) => sdk.admin.productType.create(payload),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryKey: productTypesQueryKeys.lists() })
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
export const useUpdateProductType = (
id: string,
options?: UseMutationOptions<
HttpTypes.AdminProductTypeResponse,
FetchError,
HttpTypes.AdminUpdateProductType
>
) => {
return useMutation({
mutationFn: (payload) => sdk.admin.productType.update(id, payload),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: productTypesQueryKeys.detail(id),
})
queryClient.invalidateQueries({ queryKey: productTypesQueryKeys.lists() })
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
export const useDeleteProductType = (
id: string,
options?: UseMutationOptions<
HttpTypes.AdminProductTypeDeleteResponse,
FetchError,
void
>
) => {
return useMutation({
mutationFn: () => sdk.admin.productType.delete(id),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: productTypesQueryKeys.detail(id),
})
queryClient.invalidateQueries({ queryKey: productTypesQueryKeys.lists() })
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
@@ -1,3 +1,4 @@
import { FetchError } from "@medusajs/js-sdk"
import { HttpTypes } from "@medusajs/types"
import {
QueryKey,
@@ -223,11 +224,11 @@ export const useProduct = (
}
export const useProducts = (
query?: Record<string, any>,
query?: HttpTypes.AdminProductListParams,
options?: Omit<
UseQueryOptions<
HttpTypes.AdminProductListResponse,
Error,
FetchError,
HttpTypes.AdminProductListResponse,
QueryKey
>,
@@ -0,0 +1,17 @@
import { useTranslation } from "react-i18next"
import { Filter } from "../../../components/table/data-table"
export const useDateTableFilters = () => {
const { t } = useTranslation()
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 dateFilters
}
@@ -7,6 +7,7 @@ const excludeableFields = [
"sales_channel_id",
"collections",
"categories",
"product_types",
] as const
export const useProductTableFilters = (
@@ -14,10 +15,17 @@ export const useProductTableFilters = (
) => {
const { t } = useTranslation()
const { product_types } = useProductTypes({
limit: 1000,
offset: 0,
})
const isProductTypeExcluded = exclude?.includes("product_types")
const { product_types } = useProductTypes(
{
limit: 1000,
offset: 0,
},
{
enabled: !isProductTypeExcluded,
}
)
// const { product_tags } = useAdminProductTags({
// limit: 1000,
@@ -61,7 +69,7 @@ export const useProductTableFilters = (
let filters: Filter[] = []
if (product_types) {
if (product_types && !isProductTypeExcluded) {
const typeFilter: Filter = {
key: "type_id",
label: t("fields.type"),
@@ -44,7 +44,7 @@ export const useProductTableQuery = ({
q,
} = queryObject
const searchParams: HttpTypes.AdminProductParams = {
const searchParams: HttpTypes.AdminProductListParams = {
limit: pageSize,
offset: offset ? Number(offset) : 0,
sales_channel_id: sales_channel_id?.split(","),
@@ -1647,6 +1647,25 @@
}
}
},
"productTypes": {
"domain": "Product Types",
"create": {
"header": "Create Product Type",
"hint": "Create a new product type to categorize your products.",
"successToast": "Product type {{value}} was successfully created."
},
"edit": {
"header": "Edit Product Type",
"successToast": "Product type {{value}} was successfully updated."
},
"delete": {
"confirmation": "You are about to delete the product type {{value}}. This action cannot be undone.",
"successToast": "Product type {{value}} was successfully deleted."
},
"fields": {
"value": "Value"
}
},
"errors": {
"serverError": "Server error - Try again later.",
"invalidCredentials": "Wrong email or password"
@@ -1758,8 +1777,8 @@
"sent": "Sent",
"salesChannels": "Sales Channels",
"product": "Product",
"createdAt": "Created at",
"updatedAt": "Updated at",
"createdAt": "Created",
"updatedAt": "Updated",
"revokedAt": "Revoked at",
"true": "True",
"false": "False",
@@ -848,6 +848,43 @@ export const RouteMap: RouteObject[] = [
},
],
},
{
path: "product-types",
element: <Outlet />,
handle: {
crumb: () => "Product Types",
},
children: [
{
path: "",
lazy: () =>
import("../../routes/product-types/product-type-list"),
children: [
{
path: "create",
lazy: () =>
import("../../routes/product-types/product-type-create"),
},
],
},
{
path: ":id",
lazy: () =>
import("../../routes/product-types/product-type-detail"),
handle: {
crumb: (data: HttpTypes.AdminProductTypeResponse) =>
data.product_type.value,
},
children: [
{
path: "edit",
lazy: () =>
import("../../routes/product-types/product-type-edit"),
},
],
},
],
},
{
path: "shipping-profiles",
element: <Outlet />,
@@ -0,0 +1,42 @@
import { toast, usePrompt } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { useDeleteProductType } from "../../../../hooks/api/product-types"
export const useDeleteProductTypeAction = (id: string) => {
const { t } = useTranslation()
const prompt = usePrompt()
const { mutateAsync } = useDeleteProductType(id)
const handleDelete = async () => {
const result = await prompt({
title: t("general.areYouSure"),
description: t("productTypes.delete.confirmation"),
confirmText: t("actions.delete"),
cancelText: t("actions.cancel"),
})
if (!result) {
return
}
await mutateAsync(undefined, {
onSuccess: () => {
toast.success(t("general.success"), {
description: t("productTypes.delete.successToast"),
dismissLabel: t("actions.close"),
dismissable: true,
})
},
onError: (e) => {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
dismissable: true,
})
},
})
}
return handleDelete
}
@@ -0,0 +1,105 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Heading, Input, Text, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { z } from "zod"
import { Form } from "../../../../../components/common/form"
import {
RouteFocusModal,
useRouteModal,
} from "../../../../../components/route-modal"
import { useCreateProductType } from "../../../../../hooks/api/product-types"
const CreateProductTypeSchema = z.object({
value: z.string().min(1),
})
export const CreateProductTypeForm = () => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
const form = useForm<z.infer<typeof CreateProductTypeSchema>>({
defaultValues: {
value: "",
},
resolver: zodResolver(CreateProductTypeSchema),
})
const { mutateAsync, isPending } = useCreateProductType()
const handleSubmit = form.handleSubmit(
async (values: z.infer<typeof CreateProductTypeSchema>) => {
await mutateAsync(values, {
onSuccess: ({ product_type }) => {
toast.success(t("general.success"), {
description: t("productTypes.create.successToast", {
value: product_type.value,
}),
dismissLabel: t("actions.close"),
dismissable: true,
})
handleSuccess(`/settings/product-types/${product_type.id}`)
},
onError: (e) => {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
dismissable: true,
})
},
})
}
)
return (
<RouteFocusModal.Form form={form}>
<form onSubmit={handleSubmit} className="flex flex-col overflow-hidden">
<RouteFocusModal.Header>
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
size="small"
variant="primary"
type="submit"
isLoading={isPending}
>
{t("actions.create")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body className="flex flex-col items-center overflow-y-auto p-16">
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
<div>
<Heading>{t("productTypes.create.header")}</Heading>
<Text size="small" className="text-ui-fg-subtle">
{t("productTypes.create.hint")}
</Text>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<Form.Field
control={form.control}
name="value"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("productTypes.fields.value")}</Form.Label>
<Form.Control>
<Input {...field} />
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
</div>
</div>
</RouteFocusModal.Body>
</form>
</RouteFocusModal.Form>
)
}
@@ -0,0 +1 @@
export * from "./create-product-type-form"
@@ -0,0 +1 @@
export { ProductTypeCreate as Component } from "./product-type-create"
@@ -0,0 +1,10 @@
import { RouteFocusModal } from "../../../components/route-modal"
import { CreateProductTypeForm } from "./components/create-product-type-form"
export const ProductTypeCreate = () => {
return (
<RouteFocusModal>
<CreateProductTypeForm />
</RouteFocusModal>
)
}
@@ -0,0 +1 @@
export * from "./product-type-general-section"
@@ -0,0 +1,45 @@
import { PencilSquare, Trash } from "@medusajs/icons"
import { HttpTypes } from "@medusajs/types"
import { Container, Heading } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { useDeleteProductTypeAction } from "../../../common/hooks/use-delete-product-type-action"
type ProductTypeGeneralSectionProps = {
productType: HttpTypes.AdminProductType
}
export const ProductTypeGeneralSection = ({
productType,
}: ProductTypeGeneralSectionProps) => {
const { t } = useTranslation()
const handleDelete = useDeleteProductTypeAction(productType.id)
return (
<Container className="flex items-center justify-between">
<Heading>{productType.value}</Heading>
<ActionMenu
groups={[
{
actions: [
{
label: t("actions.edit"),
icon: <PencilSquare />,
to: "edit",
},
],
},
{
actions: [
{
label: t("actions.delete"),
icon: <Trash />,
onClick: handleDelete,
},
],
},
]}
/>
</Container>
)
}
@@ -0,0 +1 @@
export * from "./product-type-product-section"
@@ -0,0 +1,66 @@
import { HttpTypes } from "@medusajs/types"
import { Container, Heading } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { DataTable } from "../../../../../components/table/data-table"
import { useProducts } from "../../../../../hooks/api/products"
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters"
import { useProductTableQuery } from "../../../../../hooks/table/query/use-product-table-query"
import { useDataTable } from "../../../../../hooks/use-data-table"
type ProductTypeProductSectionProps = {
productType: HttpTypes.AdminProductType
}
const PAGE_SIZE = 10
export const ProductTypeProductSection = ({
productType,
}: ProductTypeProductSectionProps) => {
const { t } = useTranslation()
const { searchParams, raw } = useProductTableQuery({
pageSize: PAGE_SIZE,
})
const { products, count, isPending, isError, error } = useProducts({
...searchParams,
type_id: [productType.id],
})
const filters = useProductTableFilters(["product_types"])
const columns = useProductTableColumns()
const { table } = useDataTable({
columns,
data: products,
count: products?.length || 0,
getRowId: (row) => row.id,
pageSize: PAGE_SIZE,
})
if (isError) {
throw error
}
return (
<Container className="divide-y p-0">
<div className="px-6 py-4">
<Heading level="h2">{t("products.domain")}</Heading>
</div>
<DataTable
table={table}
filters={filters}
isLoading={isPending}
columns={columns}
count={count}
pageSize={PAGE_SIZE}
navigateTo={({ original }) => `/products/${original.id}`}
orderBy={["title", "created_at", "updated_at"]}
queryObject={raw}
search
pagination
/>
</Container>
)
}
@@ -0,0 +1,2 @@
export { productTypeLoader as loader } from "./loader"
export { ProductTypeDetail as Component } from "./product-type-detail"
@@ -0,0 +1,22 @@
import { LoaderFunctionArgs } from "react-router-dom"
import { HttpTypes } from "@medusajs/types"
import { productTypesQueryKeys } from "../../../hooks/api/product-types"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
const productTypeDetailQuery = (id: string) => ({
queryKey: productTypesQueryKeys.detail(id),
queryFn: async () => sdk.admin.productType.retrieve(id),
})
export const productTypeLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = productTypeDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminProductTypeResponse>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
}
@@ -0,0 +1,56 @@
import { Outlet, useLoaderData, useParams } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section"
import { useProductType } from "../../../hooks/api/product-types"
import { ProductTypeGeneralSection } from "./components/product-type-general-section"
import { ProductTypeProductSection } from "./components/product-type-product-section"
import { productTypeLoader } from "./loader"
import after from "virtual:medusa/widgets/product_type/details/after"
import before from "virtual:medusa/widgets/product_type/details/before"
export const ProductTypeDetail = () => {
const { id } = useParams()
const initialData = useLoaderData() as Awaited<
ReturnType<typeof productTypeLoader>
>
const { product_type, isPending, isError, error } = useProductType(
id!,
undefined,
{
initialData,
}
)
if (isPending || !product_type) {
return null
}
if (isError) {
throw error
}
return (
<div className="flex flex-col gap-y-3">
{before.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={product_type} />
</div>
)
})}
<ProductTypeGeneralSection productType={product_type} />
<ProductTypeProductSection productType={product_type} />
{after.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component data={product_type} />
</div>
)
})}
<JsonViewSection data={product_type} />
<Outlet />
</div>
)
}
@@ -0,0 +1,102 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { HttpTypes } from "@medusajs/types"
import { Button, Input, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { z } from "zod"
import { Form } from "../../../../../components/common/form"
import {
RouteDrawer,
useRouteModal,
} from "../../../../../components/route-modal"
import { useUpdateProductType } from "../../../../../hooks/api/product-types"
const EditProductTypeSchema = z.object({
value: z.string().min(1),
})
type EditProductTypeFormProps = {
productType: HttpTypes.AdminProductType
}
export const EditProductTypeForm = ({
productType,
}: EditProductTypeFormProps) => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
const form = useForm<z.infer<typeof EditProductTypeSchema>>({
defaultValues: {
value: productType.value,
},
resolver: zodResolver(EditProductTypeSchema),
})
const { mutateAsync, isPending } = useUpdateProductType(productType.id)
const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync(
{
value: data.value,
},
{
onSuccess: ({ product_type }) => {
toast.success(t("general.success"), {
description: t("productTypes.edit.successToast", {
value: product_type.value,
}),
dismissable: true,
dismissLabel: t("general.close"),
})
handleSuccess()
},
onError: (error) => {
toast.error(t("general.error"), {
description: error.message,
dismissable: true,
dismissLabel: t("general.close"),
})
},
}
)
})
return (
<RouteDrawer.Form form={form}>
<form
onSubmit={handleSubmit}
className="flex flex-1 flex-col overflow-hidden"
>
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-y-auto">
<Form.Field
control={form.control}
name="value"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("productTypes.fields.value")}</Form.Label>
<Form.Control>
<Input {...field} />
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
</RouteDrawer.Body>
<RouteDrawer.Footer>
<div className="flex items-center justify-end gap-x-2">
<RouteDrawer.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteDrawer.Close>
<Button size="small" type="submit" isLoading={isPending}>
{t("actions.save")}
</Button>
</div>
</RouteDrawer.Footer>
</form>
</RouteDrawer.Form>
)
}
@@ -0,0 +1 @@
export * from "./edit-product-type-form"
@@ -0,0 +1 @@
export { ProductTypeEdit as Component } from "./product-type-edit"
@@ -0,0 +1,28 @@
import { Heading } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { useParams } from "react-router-dom"
import { RouteDrawer } from "../../../components/route-modal"
import { useProductType } from "../../../hooks/api/product-types"
import { EditProductTypeForm } from "./components/edit-product-type-form"
export const ProductTypeEdit = () => {
const { id } = useParams()
const { t } = useTranslation()
const { product_type, isPending, isError, error } = useProductType(id!)
const ready = !isPending && !!product_type
if (isError) {
throw error
}
return (
<RouteDrawer>
<RouteDrawer.Header>
<Heading>{t("productTypes.edit.header")}</Heading>
</RouteDrawer.Header>
{ready && <EditProductTypeForm productType={product_type} />}
</RouteDrawer>
)
}
@@ -0,0 +1 @@
export * from "./product-type-list-table"
@@ -0,0 +1,41 @@
import { PencilSquare, Trash } from "@medusajs/icons"
import { HttpTypes } from "@medusajs/types"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { useDeleteProductTypeAction } from "../../../common/hooks/use-delete-product-type-action"
type ProductTypeRowActionsProps = {
productType: HttpTypes.AdminProductType
}
export const ProductTypeRowActions = ({
productType,
}: ProductTypeRowActionsProps) => {
const { t } = useTranslation()
const handleDelete = useDeleteProductTypeAction(productType.id)
return (
<ActionMenu
groups={[
{
actions: [
{
label: t("actions.edit"),
icon: <PencilSquare />,
to: `/settings/product-types/${productType.id}/edit`,
},
],
},
{
actions: [
{
label: t("actions.delete"),
icon: <Trash />,
onClick: handleDelete,
},
],
},
]}
/>
)
}
@@ -0,0 +1,65 @@
import { Button, Container, Heading } from "@medusajs/ui"
import { keepPreviousData } from "@tanstack/react-query"
import { useTranslation } from "react-i18next"
import { Link } from "react-router-dom"
import { DataTable } from "../../../../../components/table/data-table"
import { useProductTypes } from "../../../../../hooks/api/product-types"
import { useDataTable } from "../../../../../hooks/use-data-table"
import { useProductTypeTableColumns } from "./use-product-type-table-columns"
import { useProductTypeTableFilters } from "./use-product-type-table-filters"
import { useProductTypeTableQuery } from "./use-product-type-table-query"
const PAGE_SIZE = 20
export const ProductTypeListTable = () => {
const { t } = useTranslation()
const { searchParams, raw } = useProductTypeTableQuery({
pageSize: PAGE_SIZE,
})
const { product_types, count, isLoading, isError, error } = useProductTypes(
searchParams,
{
placeholderData: keepPreviousData,
}
)
const filters = useProductTypeTableFilters()
const columns = useProductTypeTableColumns()
const { table } = useDataTable({
columns,
data: product_types,
count,
getRowId: (row) => row.id,
pageSize: PAGE_SIZE,
})
if (isError) {
throw error
}
return (
<Container className="divide-y p-0">
<div className="flex items-center justify-between px-6 py-4">
<Heading>{t("productTypes.domain")}</Heading>
<Button size="small" variant="secondary" asChild>
<Link to="create">{t("actions.create")}</Link>
</Button>
</div>
<DataTable
table={table}
filters={filters}
isLoading={isLoading}
columns={columns}
pageSize={PAGE_SIZE}
count={count}
orderBy={["value", "created_at", "updated_at"]}
navigateTo={({ original }) => original.id}
queryObject={raw}
pagination
search
/>
</Container>
)
}
@@ -0,0 +1,35 @@
import { HttpTypes } from "@medusajs/types"
import { createColumnHelper } from "@tanstack/react-table"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import { ProductTypeRowActions } from "./product-table-row-actions"
const columnHelper = createColumnHelper<HttpTypes.AdminProductType>()
export const useProductTypeTableColumns = () => {
const { t } = useTranslation()
return useMemo(
() => [
columnHelper.accessor("value", {
header: () => t("productTypes.fields.value"),
cell: ({ getValue }) => getValue(),
}),
columnHelper.accessor("created_at", {
header: () => t("fields.createdAt"),
cell: ({ getValue }) => getValue(),
}),
columnHelper.accessor("updated_at", {
header: () => t("fields.updatedAt"),
cell: ({ getValue }) => getValue(),
}),
columnHelper.display({
id: "actions",
cell: ({ row }) => {
return <ProductTypeRowActions productType={row.original} />
},
}),
],
[t]
)
}
@@ -0,0 +1,7 @@
import { useDateTableFilters } from "../../../../../hooks/table/filters/use-date-table-filters"
export const useProductTypeTableFilters = () => {
const dateFilters = useDateTableFilters()
return dateFilters
}
@@ -0,0 +1,31 @@
import { useQueryParams } from "../../../../../hooks/use-query-params"
type UseProductTypeTableQueryProps = {
prefix?: string
pageSize?: number
}
export const useProductTypeTableQuery = ({
prefix,
pageSize = 20,
}: UseProductTypeTableQueryProps) => {
const queryObject = useQueryParams(
["offset", "q", "order", "created_at", "updated_at"],
prefix
)
const { offset, q, order, created_at, updated_at } = queryObject
const searchParams = {
limit: pageSize,
offset: offset ? Number(offset) : 0,
order,
created_at: created_at ? JSON.parse(created_at) : undefined,
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
q,
}
return {
searchParams,
raw: queryObject,
}
}
@@ -0,0 +1 @@
export { ProductTypeList as Component } from "./product-type-list"
@@ -0,0 +1,28 @@
import { Outlet } from "react-router-dom"
import { ProductTypeListTable } from "./components/product-type-list-table"
import after from "virtual:medusa/widgets/product_type/list/after"
import before from "virtual:medusa/widgets/product_type/list/before"
export const ProductTypeList = () => {
return (
<div className="flex flex-col gap-y-3">
{before.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component />
</div>
)
})}
<ProductTypeListTable />
{after.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component />
</div>
)
})}
<Outlet />
</div>
)
}