feat(medusa, admin-ui): Improvements to product categories (#3416)

This commit is contained in:
Riqwan Thamir
2023-03-08 16:56:49 +01:00
committed by GitHub
parent 8ed67d2d7d
commit 478d1af8d0
8 changed files with 122 additions and 17 deletions

View File

@@ -0,0 +1,11 @@
---
"@medusajs/admin-ui": patch
"@medusajs/medusa": patch
---
feat(medusa, admin-ui): Improvements to product categories
- Adds name as required in category create form
- Adds name and handle as required in category edit form
- Updates message on create/update forms
- Adds category indicators for is_internal and is_active fields in the tree list
- Fixes bug where tree is not reset when update fails

View File

@@ -0,0 +1,29 @@
import React from "react"
import IconProps from "../types/icon-type"
type TagDotIconProps = IconProps & {
outerColor: string
}
const TagDotIcon: React.FC<TagDotIconProps> = ({
size = "24px",
color = "#E5484D",
outerColor = "transparent",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<circle cx="16" cy="16" r="6" fill={outerColor} />
<circle cx="16" cy="16" r="3" fill={color} />
</svg>
)
}
export default TagDotIcon

View File

@@ -0,0 +1,20 @@
import React from "react"
import Tooltip, { TooltipProps } from "../../atoms/tooltip"
type TooltipIconProps = TooltipProps & {
icon: React.ReactNode
}
const TooltipIcon: React.FC<TooltipIconProps> = ({
content,
icon,
...props
}) => {
return (
<Tooltip content={content} side="top" {...props}>
{icon}
</Tooltip>
)
}
export default TooltipIcon

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from "react"
import React, { useCallback, useMemo } from "react"
import Nestable from "react-nestable"
import { dropRight, get, flatMap } from "lodash"
@@ -13,6 +13,7 @@ import ProductCategoryListItemDetails from "./product-category-list-item-details
import ReorderIcon from "../../../components/fundamentals/icons/reorder-icon"
import { useQueryClient } from "@tanstack/react-query"
import useNotification from "../../../hooks/use-notification"
import useToggleState from "../../../hooks/use-toggle-state"
type ProductCategoriesListProps = {
categories: ProductCategory[]
@@ -25,8 +26,8 @@ function ProductCategoriesList(props: ProductCategoriesListProps) {
const { client } = useMedusa()
const queryClient = useQueryClient()
const notification = useNotification()
const [isUpdating, setIsUpdating] = useState(false)
const [isUpdating, enableUpdating, disableUpdating] = useToggleState(false)
const [isError, enableError, disableError] = useToggleState(false)
const { categories } = props
const onItemDrop = useCallback(
@@ -35,7 +36,7 @@ function ProductCategoriesList(props: ProductCategoriesListProps) {
items: ProductCategory[]
path: number[]
}) => {
setIsUpdating(true)
enableUpdating()
let parentId = null
const { dragItem, items, targetPath } = params
const [rank] = targetPath.slice(-1)
@@ -53,16 +54,19 @@ function ProductCategoriesList(props: ProductCategoriesListProps) {
}
try {
disableError()
await client.admin.productCategories.update(dragItem.id, {
parent_category_id: parentId,
rank,
})
notification("Success", "New order saved", "success")
await queryClient.invalidateQueries(adminProductCategoryKeys.lists())
notification("Success", "Successfully updated category tree", "success")
} catch (e) {
notification("Error", "Failed to save new order", "error")
notification("Error", "Failed to update category tree", "error")
enableError()
} finally {
setIsUpdating(false)
await queryClient.invalidateQueries(adminProductCategoryKeys.lists())
disableUpdating()
}
},
[]
@@ -71,7 +75,6 @@ function ProductCategoriesList(props: ProductCategoriesListProps) {
const NestableList = useMemo(
() => (
<Nestable
collapsed
items={categories}
onChange={onItemDrop}
childrenProp="category_children"
@@ -98,7 +101,7 @@ function ProductCategoriesList(props: ProductCategoriesListProps) {
)}
/>
),
[categories]
[categories, isError]
)
return (

View File

@@ -8,11 +8,14 @@ import { ProductCategoriesContext } from "../pages"
import Tooltip from "../../../components/atoms/tooltip"
import Button from "../../../components/fundamentals/button"
import Actionables from "../../../components/molecules/actionables"
import TooltipIcon from "../../../components/molecules/tooltip-icon"
import TrashIcon from "../../../components/fundamentals/icons/trash-icon"
import EditIcon from "../../../components/fundamentals/icons/edit-icon"
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
import FolderOpenIcon from "../../../components/fundamentals/icons/folder-open-icon"
import TagIcon from "../../../components/fundamentals/icons/tag-icon"
import TagDotIcon from "../../../components/fundamentals/icons/tag-dot-icon"
import EyeOffIcon from "../../../components/fundamentals/icons/eye-off-icon"
import MoreHorizontalIcon from "../../../components/fundamentals/icons/more-horizontal-icon"
import useNotification from "../../../hooks/use-notification"
@@ -70,11 +73,11 @@ function ProductCategoryListItemDetails(
<div className="flex w-full items-center justify-between">
<div className="flex items-center">
{hasChildren && (
<div className="flex w-[32px] items-center justify-center">
<div className="absolute flex w-[20px] cursor-pointer items-center justify-center">
{props.collapseIcon}
</div>
)}
<div className="flex w-[32px] items-center justify-center">
<div className="ml-[20px] flex w-[32px] items-center justify-center">
{hasChildren && <FolderOpenIcon color="#889096" size={18} />}
{!hasChildren && <TagIcon color="#889096" size={18} />}
</div>
@@ -85,6 +88,33 @@ function ProductCategoryListItemDetails(
>
{item.name}
</span>
<div className="flex w-[64px] items-center justify-center">
{!item.is_active && (
<TooltipIcon
content="Category status is inactive"
icon={
<TagDotIcon
size="32"
className="cursor-pointer"
outerColor="#FFE5E5"
/>
}
/>
)}
{item.is_internal && (
<TooltipIcon
content="Category visibility is private"
icon={
<EyeOffIcon
color="#889096"
size={18}
className="cursor-pointer"
/>
}
/>
)}
</div>
</div>
<div className="flex items-center gap-2">

View File

@@ -59,9 +59,11 @@ function CreateProductCategory(props: CreateProductCategoryProps) {
// TODO: temporary here, investigate why `useAdminCreateProductCategory` doesn't invalidate this
await queryClient.invalidateQueries(adminProductCategoryKeys.lists())
closeModal()
notification("Success", "Created a new product category", "success")
notification("Success", "Successfully created a category", "success")
} catch (e) {
notification("Error", "Failed to create a new product category", "error")
const errorMessage =
e.response?.data?.message || "Failed to create a new category"
notification("Error", errorMessage, "error")
}
}
@@ -95,6 +97,7 @@ function CreateProductCategory(props: CreateProductCategoryProps) {
<div className="mb-8 flex justify-between gap-6">
<InputField
required
label="Name"
type="string"
name="name"

View File

@@ -66,11 +66,13 @@ function EditProductCategoriesSideModal(
is_internal: !isPublic,
})
notification("Success", "Product category updated", "success")
notification("Success", "Successfully updated the category", "success")
close()
} catch (e) {
notification("Error", "Failed to update the category", "error")
const errorMessage =
e.response?.data?.message || "Failed to update the category"
notification("Error", errorMessage, "error")
}
close()
}
const onClose = () => {
@@ -98,6 +100,7 @@ function EditProductCategoriesSideModal(
<div className="flex-grow">
<InputField
required
label="Name"
type="string"
name="name"
@@ -108,6 +111,7 @@ function EditProductCategoriesSideModal(
/>
<InputField
required
label="Handle"
type="string"
name="handle"

View File

@@ -125,6 +125,11 @@ export class AdminPostProductCategoriesCategoryReq extends AdminProductCategorie
@IsOptional()
name?: string
@IsOptional()
@IsString()
@IsNotEmpty()
handle?: string
@IsOptional()
@IsInt()
@IsNotEmpty()