feat(admin-ui): added breadcrumbs for categories on create/edit modal (#3420)

What:
- Adds breadcrumbs to create modal
- Adds breadcrumbs to edit modal

<img width="581" alt="2" src="https://user-images.githubusercontent.com/5105988/223782603-f168d554-65bd-4cfc-bdcd-eabdd9f06b20.png">
<img width="1115" alt="1" src="https://user-images.githubusercontent.com/5105988/223782607-1ae441c9-c9eb-4cb0-9015-2038db55dd64.png">


RESOLVES CORE-1210
This commit is contained in:
Riqwan Thamir
2023-03-09 16:43:43 +01:00
committed by GitHub
parent 4042beb102
commit cdbc5ff3d8
6 changed files with 131 additions and 16 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/admin-ui": patch
---
feat(admin-ui): added breadcrumbs for categories on create/edit modal

View File

@@ -0,0 +1,54 @@
import React from "react"
import { ProductCategory } from "@medusajs/medusa"
import { getAncestors } from "../utils"
type TreeCrumbsProps = React.HtmlHTMLAttributes<HTMLDivElement> & {
nodes: ProductCategory[]
currentNode: ProductCategory
showPlaceholder: boolean
placeholderText: string
}
const TreeCrumbs: React.FC<TreeCrumbsProps> = ({
nodes,
currentNode,
showPlaceholder = false,
placeholderText = "",
...props
}) => {
const ancestors = getAncestors(currentNode, nodes)
return (
<span {...props}>
<span className="text-grey-40">
{ancestors.map((ancestor, index) => {
const categoryName = ancestor.name
return (
<div key={ancestor.id} className="inline-block">
<span>
{categoryName.length > 25
? categoryName.substring(0, 25) + "..."
: categoryName}
</span>
{(showPlaceholder || ancestors.length !== index + 1) && (
<span className="mx-2">/</span>
)}
</div>
)
})}
{showPlaceholder && (
<span>
<span className="border-grey-40 rounded-[10px] border-[1px] border-dashed px-[8px] py-[4px]">
{placeholderText}
</span>
</span>
)}
</span>
</span>
)
}
export default TreeCrumbs

View File

@@ -12,6 +12,7 @@ import Button from "../../../components/fundamentals/button"
import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
import InputField from "../../../components/molecules/input"
import Select from "../../../components/molecules/select"
import TreeCrumbs from "../components/tree-crumbs"
import { useQueryClient } from "@tanstack/react-query"
const visibilityOptions = [
@@ -36,7 +37,7 @@ type CreateProductCategoryProps = {
* Focus modal container for creating Publishable Keys.
*/
function CreateProductCategory(props: CreateProductCategoryProps) {
const { closeModal, parentCategory } = props
const { closeModal, parentCategory, categories } = props
const notification = useNotification()
const queryClient = useQueryClient()
@@ -90,9 +91,21 @@ function CreateProductCategory(props: CreateProductCategoryProps) {
<FocusModal.Main className="no-scrollbar flex w-full justify-center">
<div className="small:w-4/5 medium:w-7/12 large:w-6/12 my-16 max-w-[700px]">
<h1 className="inter-xlarge-semibold text-grey-90 pb-8">
<h1 className="inter-xlarge-semibold text-grey-90 pb-6">
Add category {parentCategory && `to ${parentCategory.name}`}
</h1>
{parentCategory && (
<div className="mb-6">
<TreeCrumbs
nodes={categories}
currentNode={parentCategory}
showPlaceholder={true}
placeholderText={name || "New"}
/>
</div>
)}
<h4 className="inter-large-semibold text-grey-90 pb-1">Details</h4>
<div className="mb-8 flex justify-between gap-6">

View File

@@ -9,6 +9,7 @@ import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
import InputField from "../../../components/molecules/input"
import Select from "../../../components/molecules/select"
import useNotification from "../../../hooks/use-notification"
import TreeCrumbs from "../components/tree-crumbs"
const visibilityOptions = [
{
@@ -35,7 +36,7 @@ type EditProductCategoriesSideModalProps = {
function EditProductCategoriesSideModal(
props: EditProductCategoriesSideModalProps
) {
const { isVisible, close, activeCategory } = props
const { isVisible, close, activeCategory, categories } = props
const [name, setName] = useState("")
const [handle, setHandle] = useState("")
@@ -81,10 +82,9 @@ function EditProductCategoriesSideModal(
return (
<SideModal close={onClose} isVisible={!!isVisible}>
<div className="flex h-full flex-col justify-between p-6">
<div className="flex h-full flex-col justify-between">
{/* === HEADER === */}
<div className="flex items-center justify-between">
<div className="flex items-center justify-between p-6">
<h3 className="inter-large-semibold flex items-center gap-2 text-xl text-gray-900">
Edit product category
</h3>
@@ -96,9 +96,17 @@ function EditProductCategoriesSideModal(
<CrossIcon size={20} className="text-grey-50" />
</Button>
</div>
{/* === DIVIDER === */}
<div className="flex-grow">
{/* === DIVIDER === */}
<div className="block h-[1px] bg-gray-200" />
{activeCategory && (
<div className="mt-[25px] px-6">
<TreeCrumbs nodes={categories} currentNode={activeCategory} />
</div>
)}
<div className="flex-grow px-6">
<InputField
required
label="Name"
@@ -138,15 +146,12 @@ function EditProductCategoriesSideModal(
onChange={(o) => setIsPublic(o.value === "public")}
/>
</div>
{/* === DIVIDER === */}
<div className="block h-[1px] bg-gray-200" />
<div
className="block h-[1px] bg-gray-200"
style={{ margin: "24px -24px" }}
/>
{/* === FOOTER === */}
<div className="flex justify-end gap-2">
<div className="flex justify-end gap-2 p-3">
<Button size="small" variant="ghost" onClick={onClose}>
Cancel
</Button>

View File

@@ -8,6 +8,7 @@ import BodyCard from "../../../components/organisms/body-card"
import CreateProductCategory from "../modals/add-product-category"
import ProductCategoriesList from "../components/product-categories-list"
import EditProductCategoriesSideModal from "../modals/edit-product-category"
import { flattenCategoryTree } from "../utils"
/**
* Product categories empty state placeholder.
@@ -46,7 +47,7 @@ function ProductCategoryPage() {
const [activeCategory, setActiveCategory] = useState<ProductCategory>()
const { product_categories: categories, isLoading } =
const { product_categories: categories = [], isLoading } =
useAdminProductCategories({
parent_category_id: "null",
include_descendants_tree: true,
@@ -59,7 +60,7 @@ function ProductCategoryPage() {
},
]
const showPlaceholder = !isLoading && !categories?.length
const showPlaceholder = !isLoading && !categories.length
const editCategory = (category: ProductCategory) => {
setActiveCategory(category)
@@ -67,10 +68,14 @@ function ProductCategoryPage() {
}
const createSubCategory = (category: ProductCategory) => {
if (isLoading) {
return
}
setActiveCategory(category)
showCreateModal()
}
const flattenedCategories = flattenCategoryTree(categories)
const context = {
editCategory,
createSubCategory,
@@ -97,6 +102,7 @@ function ProductCategoryPage() {
{isCreateModalVisible && (
<CreateProductCategory
parentCategory={activeCategory}
categories={flattenedCategories}
closeModal={() => {
hideCreateModal()
setActiveCategory(undefined)
@@ -108,6 +114,7 @@ function ProductCategoryPage() {
close={hideEditModal}
activeCategory={activeCategory}
isVisible={!!activeCategory && isEditModalVisible}
categories={flattenedCategories}
/>
</div>
</div>

View File

@@ -0,0 +1,31 @@
export const flattenCategoryTree = (rootCategories) => {
return rootCategories.reduce((acc, category) => {
if (category?.category_children.length) {
acc = acc
.concat(flattenCategoryTree(category.category_children))
.concat(category)
} else {
acc.push(category)
}
return acc
}, [])
}
export const getAncestors = (targetNode, nodes, acc = []) => {
let parentCategory = null
acc.push(targetNode)
if (targetNode.parent_category_id) {
parentCategory = nodes.find((n) => n.id === targetNode.parent_category_id)
acc = getAncestors(parentCategory, nodes, acc)
}
if (!parentCategory) {
return acc.reverse()
}
return acc
}