@csbeker/medusa-product-attributes 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +9012 -0
- package/.medusa/server/src/admin/index.mjs +9010 -0
- package/.medusa/server/src/api/admin/middlewares.js +10 -0
- package/.medusa/server/src/api/admin/plugin/attribute-set/[id]/attributes/route.js +17 -0
- package/.medusa/server/src/api/admin/plugin/attribute-set/[id]/route.js +33 -0
- package/.medusa/server/src/api/admin/plugin/attribute-set/middlewares.js +44 -0
- package/.medusa/server/src/api/admin/plugin/attribute-set/query-config.js +22 -0
- package/.medusa/server/src/api/admin/plugin/attribute-set/route.js +25 -0
- package/.medusa/server/src/api/admin/plugin/attribute-set/validators.js +37 -0
- package/.medusa/server/src/api/admin/plugin/attributes/[id]/route.js +68 -0
- package/.medusa/server/src/api/admin/plugin/attributes/[id]/values/[valueId]/route.js +37 -0
- package/.medusa/server/src/api/admin/plugin/attributes/[id]/values/route.js +31 -0
- package/.medusa/server/src/api/admin/plugin/attributes/middlewares.js +103 -0
- package/.medusa/server/src/api/admin/plugin/attributes/query-config.js +41 -0
- package/.medusa/server/src/api/admin/plugin/attributes/route.js +28 -0
- package/.medusa/server/src/api/admin/plugin/attributes/validators.js +69 -0
- package/.medusa/server/src/api/admin/plugin/route.js +7 -0
- package/.medusa/server/src/api/middlewares.js +12 -0
- package/.medusa/server/src/api/store/middlewares.js +8 -0
- package/.medusa/server/src/api/store/plugin/attributes/middlewares.js +64 -0
- package/.medusa/server/src/api/store/plugin/attributes/products/middlewares.js +100 -0
- package/.medusa/server/src/api/store/plugin/attributes/products/query-config.js +20 -0
- package/.medusa/server/src/api/store/plugin/attributes/products/route.js +48 -0
- package/.medusa/server/src/api/store/plugin/attributes/products/validators.js +39 -0
- package/.medusa/server/src/api/store/plugin/attributes/query-config.js +21 -0
- package/.medusa/server/src/api/store/plugin/attributes/route.js +15 -0
- package/.medusa/server/src/api/store/plugin/attributes/validators.js +14 -0
- package/.medusa/server/src/api/store/plugin/route.js +7 -0
- package/.medusa/server/src/api/utils/common-validators.js +23 -0
- package/.medusa/server/src/api/utils/constants.js +6 -0
- package/.medusa/server/src/api/utils/middlewares.js +34 -0
- package/.medusa/server/src/links/attribute-product-category.js +16 -0
- package/.medusa/server/src/links/attribute-value-product.js +16 -0
- package/.medusa/server/src/modules/attribute/events/index.js +8 -0
- package/.medusa/server/src/modules/attribute/index.js +13 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250319161229.js +24 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250320182643.js +16 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250321162638.js +14 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250505144933.js +23 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250505201747.js +21 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250506162300.js +14 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250611160552.js +14 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250611173345.js +16 -0
- package/.medusa/server/src/modules/attribute/migrations/Migration20250612192857.js +16 -0
- package/.medusa/server/src/modules/attribute/models/attribute-possible-value.js +24 -0
- package/.medusa/server/src/modules/attribute/models/attribute-set.js +22 -0
- package/.medusa/server/src/modules/attribute/models/attribute-value.js +17 -0
- package/.medusa/server/src/modules/attribute/models/attribute.js +27 -0
- package/.medusa/server/src/modules/attribute/service.js +84 -0
- package/.medusa/server/src/modules/attribute/types/attribute/common.js +13 -0
- package/.medusa/server/src/modules/attribute/types/attribute/index.js +18 -0
- package/.medusa/server/src/modules/attribute/types/attribute-set/index.js +18 -0
- package/.medusa/server/src/modules/attribute/types/attribute-set/mutations.js +3 -0
- package/.medusa/server/src/modules/attribute/types/attribute-value/index.js +18 -0
- package/.medusa/server/src/modules/attribute/types/attribute-value/mutations.js +3 -0
- package/.medusa/server/src/modules/attribute/types/index.js +20 -0
- package/.medusa/server/src/types/attribute/common.js +3 -0
- package/.medusa/server/src/types/attribute/http/attribute/admin/index.js +3 -0
- package/.medusa/server/src/types/attribute/http/attribute/index.js +18 -0
- package/.medusa/server/src/types/attribute/http/attribute-set/index.js +3 -0
- package/.medusa/server/src/types/attribute/http/index.js +19 -0
- package/.medusa/server/src/types/attribute/index.js +19 -0
- package/.medusa/server/src/utils/index.js +18 -0
- package/.medusa/server/src/utils/products-created-handler.js +22 -0
- package/.medusa/server/src/utils/products-updated-handler.js +58 -0
- package/.medusa/server/src/utils/validate-attribute-values-to-link.js +43 -0
- package/.medusa/server/src/workflows/attribute/index.js +19 -0
- package/.medusa/server/src/workflows/attribute/steps/create-attribute-possible-values.js +18 -0
- package/.medusa/server/src/workflows/attribute/steps/create-attributes.js +27 -0
- package/.medusa/server/src/workflows/attribute/steps/delete-attribute.js +31 -0
- package/.medusa/server/src/workflows/attribute/steps/index.js +21 -0
- package/.medusa/server/src/workflows/attribute/steps/update-attributes.js +34 -0
- package/.medusa/server/src/workflows/attribute/workflows/create-attribute-possible-values.js +11 -0
- package/.medusa/server/src/workflows/attribute/workflows/create-attributes.js +46 -0
- package/.medusa/server/src/workflows/attribute/workflows/delete-attribute.js +10 -0
- package/.medusa/server/src/workflows/attribute/workflows/index.js +20 -0
- package/.medusa/server/src/workflows/attribute/workflows/update-attributes.js +73 -0
- package/.medusa/server/src/workflows/attribute-set/steps/batch-link-attribute-set-attributes.js +66 -0
- package/.medusa/server/src/workflows/attribute-set/steps/create-attribute-set.js +24 -0
- package/.medusa/server/src/workflows/attribute-set/steps/index.js +20 -0
- package/.medusa/server/src/workflows/attribute-set/steps/update-attribute-set.js +22 -0
- package/.medusa/server/src/workflows/attribute-set/workflows/batch-link-attribute-set-attributes.js +10 -0
- package/.medusa/server/src/workflows/attribute-set/workflows/create-attribute-set.js +11 -0
- package/.medusa/server/src/workflows/attribute-set/workflows/index.js +20 -0
- package/.medusa/server/src/workflows/attribute-set/workflows/update-attribute-set.js +10 -0
- package/.medusa/server/src/workflows/attribute-value/steps/create-attribute-value.js +18 -0
- package/.medusa/server/src/workflows/attribute-value/steps/delete-attribute-value.js +18 -0
- package/.medusa/server/src/workflows/attribute-value/steps/index.js +20 -0
- package/.medusa/server/src/workflows/attribute-value/steps/validate-attribute-value.js +53 -0
- package/.medusa/server/src/workflows/attribute-value/workflow/create-attribute-value.js +28 -0
- package/.medusa/server/src/workflows/attribute-value/workflow/delete-attribute-value.js +38 -0
- package/.medusa/server/src/workflows/attribute-value/workflow/index.js +19 -0
- package/.medusa/server/src/workflows/attribute_possible_value/index.js +19 -0
- package/.medusa/server/src/workflows/attribute_possible_value/steps/index.js +18 -0
- package/.medusa/server/src/workflows/attribute_possible_value/steps/update-attribute-possible-value.js +18 -0
- package/.medusa/server/src/workflows/attribute_possible_value/workflows/index.js +18 -0
- package/.medusa/server/src/workflows/attribute_possible_value/workflows/update-attribute-possible-value.js +11 -0
- package/.medusa/server/src/workflows/index.js +18 -0
- package/CHANGELOG.md +104 -0
- package/README.md +86 -0
- package/package.json +90 -0
- package/src/admin/README.md +31 -0
- package/src/admin/components/metadata-editor/index.tsx +101 -0
- package/src/admin/components/section-row.tsx +41 -0
- package/src/admin/hooks/api/attribute-set.ts +122 -0
- package/src/admin/hooks/api/attributes.ts +126 -0
- package/src/admin/hooks/table/columns/index.ts +1 -0
- package/src/admin/hooks/table/columns/use-attribute-table-columns.tsx +280 -0
- package/src/admin/layouts/single-column.tsx +11 -0
- package/src/admin/lib/config.ts +8 -0
- package/src/admin/lib/query-key-factory.ts +53 -0
- package/src/admin/routes/attributes/[id]/edit/page.tsx +133 -0
- package/src/admin/routes/attributes/[id]/edit-possible-value/page.tsx +174 -0
- package/src/admin/routes/attributes/[id]/page.tsx +127 -0
- package/src/admin/routes/attributes/components/AttributeForm.tsx +301 -0
- package/src/admin/routes/attributes/components/AttributeSetTable.tsx +108 -0
- package/src/admin/routes/attributes/components/category-selection-modal.tsx +82 -0
- package/src/admin/routes/attributes/components/possible-values-table.tsx +119 -0
- package/src/admin/routes/attributes/create/components/MultiSelectCategory.tsx +148 -0
- package/src/admin/routes/attributes/create/components/PossibleValuesList.tsx +151 -0
- package/src/admin/routes/attributes/create/page.tsx +123 -0
- package/src/admin/routes/attributes/create-set/page.tsx +110 -0
- package/src/admin/routes/attributes/page.tsx +346 -0
- package/src/admin/routes/attributes/set/[id]/attributes/page.tsx +35 -0
- package/src/admin/routes/attributes/set/[id]/components/AttributeSetAttributesSection.tsx +114 -0
- package/src/admin/routes/attributes/set/[id]/components/AttributeSetGeneralSection.tsx +42 -0
- package/src/admin/routes/attributes/set/[id]/components/attribute-set-attributes-form.tsx +143 -0
- package/src/admin/routes/attributes/set/[id]/components/index.ts +2 -0
- package/src/admin/routes/attributes/set/[id]/edit/page.tsx +119 -0
- package/src/admin/routes/attributes/set/[id]/page.tsx +45 -0
- package/src/admin/tsconfig.json +27 -0
- package/src/admin/types/global.d.ts +3 -0
- package/src/admin/vite-env.d.ts +1 -0
- package/src/api/README.md +133 -0
- package/src/api/admin/middlewares.ts +8 -0
- package/src/api/admin/plugin/attribute-set/[id]/attributes/route.ts +17 -0
- package/src/api/admin/plugin/attribute-set/[id]/route.ts +41 -0
- package/src/api/admin/plugin/attribute-set/middlewares.ts +42 -0
- package/src/api/admin/plugin/attribute-set/query-config.ts +20 -0
- package/src/api/admin/plugin/attribute-set/route.ts +34 -0
- package/src/api/admin/plugin/attribute-set/validators.ts +45 -0
- package/src/api/admin/plugin/attributes/[id]/route.ts +85 -0
- package/src/api/admin/plugin/attributes/[id]/values/[valueId]/route.ts +41 -0
- package/src/api/admin/plugin/attributes/[id]/values/route.ts +39 -0
- package/src/api/admin/plugin/attributes/middlewares.ts +91 -0
- package/src/api/admin/plugin/attributes/query-config.ts +42 -0
- package/src/api/admin/plugin/attributes/route.ts +33 -0
- package/src/api/admin/plugin/attributes/validators.ts +91 -0
- package/src/api/admin/plugin/route.ts +8 -0
- package/src/api/middlewares.ts +10 -0
- package/src/api/store/middlewares.ts +6 -0
- package/src/api/store/plugin/attributes/middlewares.ts +33 -0
- package/src/api/store/plugin/attributes/products/middlewares.ts +73 -0
- package/src/api/store/plugin/attributes/products/query-config.ts +19 -0
- package/src/api/store/plugin/attributes/products/route.ts +68 -0
- package/src/api/store/plugin/attributes/products/validators.ts +55 -0
- package/src/api/store/plugin/attributes/query-config.ts +19 -0
- package/src/api/store/plugin/attributes/route.ts +13 -0
- package/src/api/store/plugin/attributes/validators.ts +14 -0
- package/src/api/store/plugin/route.ts +8 -0
- package/src/api/utils/common-validators.ts +24 -0
- package/src/api/utils/constants.ts +2 -0
- package/src/api/utils/middlewares.ts +31 -0
- package/src/jobs/README.md +36 -0
- package/src/links/README.md +26 -0
- package/src/links/attribute-product-category.ts +14 -0
- package/src/links/attribute-value-product.ts +14 -0
- package/src/modules/README.md +116 -0
- package/src/modules/attribute/events/index.ts +4 -0
- package/src/modules/attribute/index.ts +8 -0
- package/src/modules/attribute/migrations/.snapshot-medusa-attribute.json +624 -0
- package/src/modules/attribute/migrations/Migration20250319161229.ts +27 -0
- package/src/modules/attribute/migrations/Migration20250320182643.ts +15 -0
- package/src/modules/attribute/migrations/Migration20250321162638.ts +13 -0
- package/src/modules/attribute/migrations/Migration20250505144933.ts +26 -0
- package/src/modules/attribute/migrations/Migration20250505201747.ts +23 -0
- package/src/modules/attribute/migrations/Migration20250506162300.ts +13 -0
- package/src/modules/attribute/migrations/Migration20250611160552.ts +13 -0
- package/src/modules/attribute/migrations/Migration20250611173345.ts +17 -0
- package/src/modules/attribute/migrations/Migration20250612192857.ts +17 -0
- package/src/modules/attribute/models/attribute-possible-value.ts +20 -0
- package/src/modules/attribute/models/attribute-set.ts +18 -0
- package/src/modules/attribute/models/attribute-value.ts +13 -0
- package/src/modules/attribute/models/attribute.ts +23 -0
- package/src/modules/attribute/service.ts +102 -0
- package/src/modules/attribute/types/attribute/common.ts +94 -0
- package/src/modules/attribute/types/attribute/index.ts +1 -0
- package/src/modules/attribute/types/attribute-set/index.ts +1 -0
- package/src/modules/attribute/types/attribute-set/mutations.ts +7 -0
- package/src/modules/attribute/types/attribute-value/index.ts +1 -0
- package/src/modules/attribute/types/attribute-value/mutations.ts +5 -0
- package/src/modules/attribute/types/index.ts +3 -0
- package/src/providers/README.md +30 -0
- package/src/subscribers/README.md +59 -0
- package/src/types/attribute/common.ts +173 -0
- package/src/types/attribute/http/attribute/admin/index.ts +0 -0
- package/src/types/attribute/http/attribute/index.ts +42 -0
- package/src/types/attribute/http/attribute-set/index.ts +10 -0
- package/src/types/attribute/http/index.ts +2 -0
- package/src/types/attribute/index.ts +2 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/products-created-handler.ts +35 -0
- package/src/utils/products-updated-handler.ts +74 -0
- package/src/utils/validate-attribute-values-to-link.ts +67 -0
- package/src/workflows/README.md +79 -0
- package/src/workflows/attribute/index.ts +2 -0
- package/src/workflows/attribute/steps/create-attribute-possible-values.ts +29 -0
- package/src/workflows/attribute/steps/create-attributes.ts +35 -0
- package/src/workflows/attribute/steps/delete-attribute.ts +41 -0
- package/src/workflows/attribute/steps/index.ts +4 -0
- package/src/workflows/attribute/steps/update-attributes.ts +45 -0
- package/src/workflows/attribute/workflows/create-attribute-possible-values.ts +17 -0
- package/src/workflows/attribute/workflows/create-attributes.ts +56 -0
- package/src/workflows/attribute/workflows/delete-attribute.ts +15 -0
- package/src/workflows/attribute/workflows/index.ts +3 -0
- package/src/workflows/attribute/workflows/update-attributes.ts +103 -0
- package/src/workflows/attribute-set/steps/batch-link-attribute-set-attributes.ts +82 -0
- package/src/workflows/attribute-set/steps/create-attribute-set.ts +34 -0
- package/src/workflows/attribute-set/steps/index.ts +3 -0
- package/src/workflows/attribute-set/steps/update-attribute-set.ts +32 -0
- package/src/workflows/attribute-set/workflows/batch-link-attribute-set-attributes.ts +12 -0
- package/src/workflows/attribute-set/workflows/create-attribute-set.ts +17 -0
- package/src/workflows/attribute-set/workflows/index.ts +3 -0
- package/src/workflows/attribute-set/workflows/update-attribute-set.ts +14 -0
- package/src/workflows/attribute-value/steps/create-attribute-value.ts +26 -0
- package/src/workflows/attribute-value/steps/delete-attribute-value.ts +26 -0
- package/src/workflows/attribute-value/steps/index.ts +3 -0
- package/src/workflows/attribute-value/steps/validate-attribute-value.ts +95 -0
- package/src/workflows/attribute-value/workflow/create-attribute-value.ts +36 -0
- package/src/workflows/attribute-value/workflow/delete-attribute-value.ts +46 -0
- package/src/workflows/attribute-value/workflow/index.ts +2 -0
- package/src/workflows/attribute_possible_value/index.ts +2 -0
- package/src/workflows/attribute_possible_value/steps/index.ts +1 -0
- package/src/workflows/attribute_possible_value/steps/update-attribute-possible-value.ts +25 -0
- package/src/workflows/attribute_possible_value/workflows/index.ts +1 -0
- package/src/workflows/attribute_possible_value/workflows/update-attribute-possible-value.ts +15 -0
- package/src/workflows/index.ts +1 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Text, Badge, IconButton } from "@medusajs/ui"
|
|
2
|
+
import { AdminProductCategory } from "@medusajs/types"
|
|
3
|
+
import React, { useState, useRef, useEffect } from "react"
|
|
4
|
+
import { TrianglesMini, XMarkMini, ChevronRightMini, ChevronLeftMini, ArrowUturnLeft, PlayMiniSolid, TriangleRightMiniHover } from "@medusajs/icons"
|
|
5
|
+
|
|
6
|
+
type MultiSelectCategoryProps = {
|
|
7
|
+
categories: AdminProductCategory[]
|
|
8
|
+
value: string[]
|
|
9
|
+
onChange: (value: string[]) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MultiSelectCategory: React.FC<MultiSelectCategoryProps> = ({
|
|
13
|
+
categories,
|
|
14
|
+
value,
|
|
15
|
+
onChange,
|
|
16
|
+
}) => {
|
|
17
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
18
|
+
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
19
|
+
const triggerRef = useRef<HTMLDivElement>(null)
|
|
20
|
+
const [currentParentId, setCurrentParentId] = useState<string | null>(null)
|
|
21
|
+
const [pathHistory, setPathHistory] = useState<(string | null)[]>([])
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
25
|
+
if (
|
|
26
|
+
dropdownRef.current &&
|
|
27
|
+
!dropdownRef.current.contains(event.target as Node) &&
|
|
28
|
+
triggerRef.current &&
|
|
29
|
+
!triggerRef.current.contains(event.target as Node)
|
|
30
|
+
) {
|
|
31
|
+
setIsOpen(false)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
document.addEventListener("mousedown", handleClickOutside)
|
|
35
|
+
return () => {
|
|
36
|
+
document.removeEventListener("mousedown", handleClickOutside)
|
|
37
|
+
}
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
const handleToggle = () => {
|
|
41
|
+
setIsOpen((prev) => !prev)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hasChildren = (categoryId: string) => {
|
|
45
|
+
return categories.some(cat => cat.parent_category_id === categoryId)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleDrillDown = (category: AdminProductCategory, event: React.MouseEvent) => {
|
|
49
|
+
event.stopPropagation() // Prevent selection when drilling down
|
|
50
|
+
setPathHistory([...pathHistory, currentParentId])
|
|
51
|
+
setCurrentParentId(category.id)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const handleGoBack = () => {
|
|
55
|
+
const newPathHistory = [...pathHistory]
|
|
56
|
+
const previousParentId = newPathHistory.pop()
|
|
57
|
+
setPathHistory(newPathHistory)
|
|
58
|
+
setCurrentParentId(previousParentId || null)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const handleItemClick = (categoryId: string) => {
|
|
62
|
+
const isSelected = value.includes(categoryId)
|
|
63
|
+
if (isSelected) {
|
|
64
|
+
onChange(value.filter((id) => id !== categoryId))
|
|
65
|
+
} else {
|
|
66
|
+
onChange([...value, categoryId])
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const currentCategories = categories.filter(cat => cat.parent_category_id === currentParentId)
|
|
71
|
+
|
|
72
|
+
const getBackButtonText = (): string => {
|
|
73
|
+
const parentCategory = categories.find(cat => cat.id === currentParentId);
|
|
74
|
+
return parentCategory?.name || "";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="relative">
|
|
79
|
+
<div
|
|
80
|
+
ref={triggerRef}
|
|
81
|
+
className="relative flex h-10 w-full cursor-pointer items-center justify-between overflow-hidden rounded-md border border-ui-border-base bg-ui-bg-field text-ui-fg-base shadow-sm transition-colors duration-150 ease-in-out hover:bg-ui-bg-field-hover focus-within:border-ui-border-interactive focus-within:ring-1 focus-within:ring-ui-ring-interactive"
|
|
82
|
+
onClick={handleToggle}
|
|
83
|
+
>
|
|
84
|
+
<div className="flex items-center gap-2 px-3 py-2">
|
|
85
|
+
{value.length > 0 ? (
|
|
86
|
+
<>
|
|
87
|
+
<button type="button" onClick={(e) => { e.stopPropagation(); onChange([]) }}>
|
|
88
|
+
<Badge size="small" className="w-fit">
|
|
89
|
+
{value.length}
|
|
90
|
+
<XMarkMini></XMarkMini>
|
|
91
|
+
</Badge>
|
|
92
|
+
</button>
|
|
93
|
+
</>
|
|
94
|
+
) : (
|
|
95
|
+
<Text className="text-ui-fg-subtle">Select categories</Text>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
<span className="flex h-full w-10 items-center justify-center border-l border-ui-border-base">
|
|
99
|
+
<TrianglesMini></TrianglesMini>
|
|
100
|
+
</span>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{isOpen && (
|
|
104
|
+
<div
|
|
105
|
+
ref={dropdownRef}
|
|
106
|
+
className="absolute z-10 mt-1 w-full max-h-60 overflow-y-auto rounded-md border border-ui-border-base bg-ui-bg-base shadow-lg"
|
|
107
|
+
>
|
|
108
|
+
{currentParentId !== null && (
|
|
109
|
+
<div
|
|
110
|
+
className="flex cursor-pointer items-center gap-3 px-3 py-2 text-ui-fg-subtle hover:bg-ui-bg-base-hover border-b border-ui-border-base"
|
|
111
|
+
onClick={handleGoBack}
|
|
112
|
+
>
|
|
113
|
+
<ArrowUturnLeft />
|
|
114
|
+
<Text>{getBackButtonText()}</Text>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
{currentCategories.length === 0 ? (
|
|
118
|
+
<div className="p-3 text-ui-fg-subtle">No categories found.</div>
|
|
119
|
+
) : (
|
|
120
|
+
currentCategories.map((category) => {
|
|
121
|
+
const isSelected = value.includes(category.id);
|
|
122
|
+
const hasChildrenNode = hasChildren(category.id)
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
key={category.id}
|
|
126
|
+
className={`flex cursor-pointer items-center justify-between px-1 py-1`}
|
|
127
|
+
onClick={() => handleItemClick(category.id)}
|
|
128
|
+
>
|
|
129
|
+
<div className="flex items-center hover:bg-ui-bg-base-hover flex-1 px-2 mr-2 py-1.5 rounded-md relative">
|
|
130
|
+
{isSelected && <span className="absolute left-3 top-1/2 -translate-y-1/2 w-1 h-1 bg-white rounded-full" />}
|
|
131
|
+
<Text className="ml-6">{category.name}</Text>
|
|
132
|
+
</div>
|
|
133
|
+
{hasChildrenNode && (
|
|
134
|
+
<div onClick={(e) => handleDrillDown(category, e)} className="p-2 rounded-md hover:bg-ui-bg-base-hover">
|
|
135
|
+
<TriangleRightMiniHover className="mr-1" />
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
})
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default MultiSelectCategory
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core'
|
|
2
|
+
import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'
|
|
3
|
+
import { CSS } from '@dnd-kit/utilities'
|
|
4
|
+
import { Button, IconButton, Input, Text } from '@medusajs/ui'
|
|
5
|
+
import { Plus, X, EllipsisHorizontal, XMark } from '@medusajs/icons'
|
|
6
|
+
import { useFieldArray, useFormContext } from 'react-hook-form'
|
|
7
|
+
import { AdminCreateAttributeValueType } from '../../../../../api/admin/plugin/attributes/validators'
|
|
8
|
+
import { CreateAttributeFormSchema } from '../page'
|
|
9
|
+
import { z } from 'zod'
|
|
10
|
+
|
|
11
|
+
type FormValues = z.infer<typeof CreateAttributeFormSchema>
|
|
12
|
+
|
|
13
|
+
interface SortableItemProps {
|
|
14
|
+
id: string
|
|
15
|
+
index: number
|
|
16
|
+
onRemove: () => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SortableItem = ({ id, index, onRemove }: SortableItemProps) => {
|
|
20
|
+
const { register, formState: { errors } } = useFormContext<FormValues>()
|
|
21
|
+
const {
|
|
22
|
+
attributes,
|
|
23
|
+
listeners,
|
|
24
|
+
setNodeRef,
|
|
25
|
+
transform,
|
|
26
|
+
transition,
|
|
27
|
+
} = useSortable({ id })
|
|
28
|
+
|
|
29
|
+
const style = {
|
|
30
|
+
transform: CSS.Transform.toString(transform),
|
|
31
|
+
transition,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const fieldError = errors.possible_values?.[index]?.value
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
ref={setNodeRef}
|
|
39
|
+
style={style}
|
|
40
|
+
className="flex items-center gap-2 p-2 bg-ui-bg-component border border-ui-border-base rounded-xl mb-2"
|
|
41
|
+
>
|
|
42
|
+
<button
|
|
43
|
+
className="cursor-grab active:cursor-grabbing"
|
|
44
|
+
{...attributes}
|
|
45
|
+
{...listeners}
|
|
46
|
+
>
|
|
47
|
+
<EllipsisHorizontal className="text-ui-fg-subtle" />
|
|
48
|
+
</button>
|
|
49
|
+
<div className="flex-1">
|
|
50
|
+
<Input
|
|
51
|
+
className="flex-1"
|
|
52
|
+
aria-invalid={!!fieldError}
|
|
53
|
+
placeholder="Enter value"
|
|
54
|
+
{...register(`possible_values.${index}.value`)}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
<IconButton
|
|
58
|
+
variant="transparent"
|
|
59
|
+
size="small"
|
|
60
|
+
onClick={onRemove}
|
|
61
|
+
>
|
|
62
|
+
<XMark />
|
|
63
|
+
</IconButton>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const PossibleValuesList = () => {
|
|
69
|
+
const { control, getValues } = useFormContext<FormValues>()
|
|
70
|
+
const { fields, append, remove, update } = useFieldArray({
|
|
71
|
+
control,
|
|
72
|
+
name: 'possible_values',
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const sensors = useSensors(
|
|
76
|
+
useSensor(PointerSensor),
|
|
77
|
+
useSensor(KeyboardSensor, {
|
|
78
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const handleDragEnd = (event: DragEndEvent) => {
|
|
83
|
+
const { active, over } = event
|
|
84
|
+
|
|
85
|
+
if (over && active.id !== over.id) {
|
|
86
|
+
const oldIndex = fields.findIndex((field) => field.id === active.id)
|
|
87
|
+
const newIndex = fields.findIndex((field) => field.id === over.id)
|
|
88
|
+
|
|
89
|
+
// Get current form values
|
|
90
|
+
const currentValues = getValues('possible_values') as AdminCreateAttributeValueType[]
|
|
91
|
+
|
|
92
|
+
// Create new array with reordered items
|
|
93
|
+
const reorderedValues = arrayMove(currentValues, oldIndex, newIndex)
|
|
94
|
+
|
|
95
|
+
// Update all fields with their new positions and ranks
|
|
96
|
+
reorderedValues.forEach((value, index) => {
|
|
97
|
+
update(index, {
|
|
98
|
+
value: value.value,
|
|
99
|
+
rank: index,
|
|
100
|
+
metadata: value.metadata || {},
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const handleAddValue = () => {
|
|
107
|
+
append({
|
|
108
|
+
value: '',
|
|
109
|
+
rank: fields.length,
|
|
110
|
+
metadata: {},
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="space-y-2">
|
|
116
|
+
<div className="flex items-center justify-between pb-1">
|
|
117
|
+
<Text className="text-ui-fg-subtle">Possible Values</Text>
|
|
118
|
+
<Button
|
|
119
|
+
type="button"
|
|
120
|
+
variant="secondary"
|
|
121
|
+
size="small"
|
|
122
|
+
onClick={handleAddValue}
|
|
123
|
+
>
|
|
124
|
+
Add
|
|
125
|
+
</Button>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<DndContext
|
|
129
|
+
sensors={sensors}
|
|
130
|
+
collisionDetection={closestCenter}
|
|
131
|
+
onDragEnd={handleDragEnd}
|
|
132
|
+
>
|
|
133
|
+
<SortableContext
|
|
134
|
+
items={fields.map((field) => field.id)}
|
|
135
|
+
strategy={verticalListSortingStrategy}
|
|
136
|
+
>
|
|
137
|
+
{fields.map((field, index) => (
|
|
138
|
+
<SortableItem
|
|
139
|
+
key={field.id}
|
|
140
|
+
id={field.id}
|
|
141
|
+
index={index}
|
|
142
|
+
onRemove={() => remove(index)}
|
|
143
|
+
/>
|
|
144
|
+
))}
|
|
145
|
+
</SortableContext>
|
|
146
|
+
</DndContext>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default PossibleValuesList
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { defineRouteConfig } from "@medusajs/admin-sdk";
|
|
2
|
+
import {
|
|
3
|
+
FocusModal,
|
|
4
|
+
Button,
|
|
5
|
+
toast,
|
|
6
|
+
ProgressTabs,
|
|
7
|
+
} from "@medusajs/ui";
|
|
8
|
+
import { useNavigate } from "react-router-dom";
|
|
9
|
+
import { medusaClient } from "../../../lib/config";
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
import { AdminProductCategory } from "@medusajs/types";
|
|
12
|
+
import { AttributeForm, CreateAttributeFormSchema } from "../components/AttributeForm";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
15
|
+
import { attributeQueryKeys } from "../../../hooks/api/attributes";
|
|
16
|
+
|
|
17
|
+
const CreateAttributePage = () => {
|
|
18
|
+
const navigate = useNavigate();
|
|
19
|
+
const [categories, setCategories] = useState<AdminProductCategory[]>([]);
|
|
20
|
+
const [activeTab, setActiveTab] = useState<"details" | "type">("details");
|
|
21
|
+
const [tabStatuses, setTabStatuses] = useState<{
|
|
22
|
+
detailsStatus: "not-started" | "in-progress" | "completed";
|
|
23
|
+
typeStatus: "not-started" | "in-progress" | "completed";
|
|
24
|
+
}>({
|
|
25
|
+
detailsStatus: "not-started",
|
|
26
|
+
typeStatus: "not-started",
|
|
27
|
+
});
|
|
28
|
+
const queryClient = useQueryClient();
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const fetchCategories = async () => {
|
|
32
|
+
try {
|
|
33
|
+
const response = await medusaClient.admin.productCategory.list();
|
|
34
|
+
setCategories(response.product_categories);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("Failed to fetch categories:", error);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
fetchCategories();
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const handleSave = async (data: z.infer<typeof CreateAttributeFormSchema>) => {
|
|
43
|
+
try {
|
|
44
|
+
const { ...payload } = data;
|
|
45
|
+
await medusaClient.client.fetch("/admin/plugin/attributes", {
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: payload,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
queryClient.invalidateQueries({ queryKey: attributeQueryKeys.lists() });
|
|
51
|
+
|
|
52
|
+
toast.success("Attribute created!");
|
|
53
|
+
navigate(-1);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
toast.error((error as Error).message);
|
|
56
|
+
console.error(error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleClose = () => {
|
|
61
|
+
navigate(-1);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<FocusModal
|
|
66
|
+
open={true}
|
|
67
|
+
onOpenChange={(open) => {
|
|
68
|
+
if (!open) handleClose();
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<FocusModal.Content>
|
|
72
|
+
<ProgressTabs
|
|
73
|
+
value={activeTab}
|
|
74
|
+
onValueChange={(value) => setActiveTab(value as "details" | "type")}
|
|
75
|
+
className="w-full h-full overflow-y-auto"
|
|
76
|
+
>
|
|
77
|
+
<FocusModal.Header className="flex items-center justify-between bg-ui-bg-base w-full py-0 h-fit sticky top-0 z-10">
|
|
78
|
+
<div className="w-full border-l h-full">
|
|
79
|
+
<ProgressTabs.List className="justify-start flex w-full items-center">
|
|
80
|
+
<ProgressTabs.Trigger
|
|
81
|
+
value="details"
|
|
82
|
+
status={tabStatuses.detailsStatus}
|
|
83
|
+
>
|
|
84
|
+
Details
|
|
85
|
+
</ProgressTabs.Trigger>
|
|
86
|
+
<ProgressTabs.Trigger
|
|
87
|
+
value="type"
|
|
88
|
+
status={tabStatuses.typeStatus}
|
|
89
|
+
>
|
|
90
|
+
Type
|
|
91
|
+
</ProgressTabs.Trigger>
|
|
92
|
+
</ProgressTabs.List>
|
|
93
|
+
</div>
|
|
94
|
+
</FocusModal.Header>
|
|
95
|
+
<FocusModal.Body className="flex flex-col items-center py-16">
|
|
96
|
+
<div>
|
|
97
|
+
<AttributeForm
|
|
98
|
+
//@ts-expect-error The received values will be for create form
|
|
99
|
+
onSubmit={handleSave}
|
|
100
|
+
onCancel={handleClose}
|
|
101
|
+
categories={categories}
|
|
102
|
+
activeTab={activeTab}
|
|
103
|
+
onFormStateChange={setTabStatuses}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
</FocusModal.Body>
|
|
107
|
+
</ProgressTabs>
|
|
108
|
+
<FocusModal.Footer>
|
|
109
|
+
<Button variant="secondary" onClick={handleClose}>
|
|
110
|
+
Cancel
|
|
111
|
+
</Button>
|
|
112
|
+
<Button type="submit" form="attribute-form">
|
|
113
|
+
Save
|
|
114
|
+
</Button>
|
|
115
|
+
</FocusModal.Footer>
|
|
116
|
+
</FocusModal.Content>
|
|
117
|
+
</FocusModal>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const config = defineRouteConfig({});
|
|
122
|
+
|
|
123
|
+
export default CreateAttributePage;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { defineRouteConfig } from "@medusajs/admin-sdk";
|
|
2
|
+
import {
|
|
3
|
+
FocusModal,
|
|
4
|
+
Heading,
|
|
5
|
+
Button,
|
|
6
|
+
toast,
|
|
7
|
+
Text,
|
|
8
|
+
Label,
|
|
9
|
+
Input,
|
|
10
|
+
Textarea,
|
|
11
|
+
} from "@medusajs/ui";
|
|
12
|
+
import { useNavigate } from "react-router-dom";
|
|
13
|
+
import { medusaClient } from "../../../lib/config";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { AdminCreateAttributeSet } from "../../../../api/admin/plugin/attribute-set/validators";
|
|
16
|
+
import { useForm } from "react-hook-form";
|
|
17
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
18
|
+
|
|
19
|
+
type CreateAttributeSetFormValues = z.infer<typeof AdminCreateAttributeSet>;
|
|
20
|
+
|
|
21
|
+
const CreateAttributeSetPage = () => {
|
|
22
|
+
const navigate = useNavigate();
|
|
23
|
+
const form = useForm<CreateAttributeSetFormValues>({
|
|
24
|
+
resolver: zodResolver(AdminCreateAttributeSet),
|
|
25
|
+
defaultValues: {
|
|
26
|
+
name: "",
|
|
27
|
+
handle: "",
|
|
28
|
+
description: "",
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const handleSubmit = form.handleSubmit(async (data) => {
|
|
33
|
+
try {
|
|
34
|
+
await medusaClient.client.fetch('/admin/plugin/attribute-set', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
body: data
|
|
37
|
+
})
|
|
38
|
+
toast.success('Attribute set created!')
|
|
39
|
+
navigate(-1)
|
|
40
|
+
} catch (error) {
|
|
41
|
+
toast.error((error as Error).message);
|
|
42
|
+
console.error(error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const handleClose = () => {
|
|
47
|
+
navigate(-1);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<FocusModal
|
|
52
|
+
open={true}
|
|
53
|
+
onOpenChange={(open) => {
|
|
54
|
+
if (!open) handleClose();
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<FocusModal.Content>
|
|
58
|
+
<FocusModal.Header>
|
|
59
|
+
<Heading>Create Attribute Set</Heading>
|
|
60
|
+
</FocusModal.Header>
|
|
61
|
+
<FocusModal.Body className="flex flex-col items-center py-16">
|
|
62
|
+
<div className="flex w-full max-w-lg flex-col gap-y-8">
|
|
63
|
+
<div className="flex flex-col gap-y-1">
|
|
64
|
+
<Heading>Create attribute set</Heading>
|
|
65
|
+
<Text className="text-ui-fg-subtle">
|
|
66
|
+
Create attribute sets to group together related attributes.
|
|
67
|
+
</Text>
|
|
68
|
+
</div>
|
|
69
|
+
<form id="attribute-set-form" onSubmit={handleSubmit}>
|
|
70
|
+
<div className="grid gap-4">
|
|
71
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
72
|
+
<div>
|
|
73
|
+
<Label size="xsmall" htmlFor="name">
|
|
74
|
+
Name
|
|
75
|
+
</Label>
|
|
76
|
+
<Input aria-invalid={!!form.formState.errors.name} {...form.register("name")} />
|
|
77
|
+
</div>
|
|
78
|
+
<div>
|
|
79
|
+
<Label size="xsmall" htmlFor="handle">
|
|
80
|
+
Handle
|
|
81
|
+
</Label>
|
|
82
|
+
<Input {...form.register("handle")} />
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div>
|
|
86
|
+
<Label size="xsmall" htmlFor="description">
|
|
87
|
+
Description
|
|
88
|
+
</Label>
|
|
89
|
+
<Textarea {...form.register("description")} />
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</form>
|
|
93
|
+
</div>
|
|
94
|
+
</FocusModal.Body>
|
|
95
|
+
<FocusModal.Footer>
|
|
96
|
+
<Button variant="secondary" onClick={handleClose}>
|
|
97
|
+
Cancel
|
|
98
|
+
</Button>
|
|
99
|
+
<Button type="submit" form="attribute-set-form">
|
|
100
|
+
Create
|
|
101
|
+
</Button>
|
|
102
|
+
</FocusModal.Footer>
|
|
103
|
+
</FocusModal.Content>
|
|
104
|
+
</FocusModal>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const config = defineRouteConfig({});
|
|
109
|
+
|
|
110
|
+
export default CreateAttributeSetPage;
|