From 0e8ad1b0abb122a3f23db23cf19b11c0c9f12422 Mon Sep 17 00:00:00 2001 From: Thomas Sindt Date: Tue, 11 Aug 2026 23:29:39 +0200 Subject: [PATCH] TreeView for ProductGroups inital implementation --- .../client/material-editor/package-lock.json | 68 ++++++++++++++---- buildtime/client/material-editor/package.json | 1 + buildtime/client/material-editor/src/App.tsx | 2 + .../src/api/language/productGroup.api.ts | 22 ++++++ .../material-editor/src/components/NavBar.tsx | 2 +- .../src/components/ProductGroupTreeView.tsx | 27 +++++++ .../material-editor/src/data/product.ts | 9 +++ .../material-editor/src/data/productGroup.ts | 2 +- .../material-editor/src/pages/ProductPage.tsx | 40 +++++++++++ ...mp-simply_cpq_master_data-202608112212.sql | Bin 0 -> 76932 bytes 10 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 buildtime/client/material-editor/src/api/language/productGroup.api.ts create mode 100644 buildtime/client/material-editor/src/components/ProductGroupTreeView.tsx create mode 100644 buildtime/client/material-editor/src/pages/ProductPage.tsx create mode 100644 database/dump-simply_cpq_master_data-202608112212.sql diff --git a/buildtime/client/material-editor/package-lock.json b/buildtime/client/material-editor/package-lock.json index 477df4d6b..891c3ba50 100644 --- a/buildtime/client/material-editor/package-lock.json +++ b/buildtime/client/material-editor/package-lock.json @@ -14,6 +14,7 @@ "@mui/icons-material": "^9.2.0", "@mui/material": "^9.2.0", "@mui/x-data-grid": "^9.10.1", + "@mui/x-tree-view": "^9.11.0", "keycloak-js": "^26.2.4", "ky": "^2.0.2", "react": "^19.2.8", @@ -924,12 +925,12 @@ } }, "node_modules/@mui/types": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.1.1.tgz", - "integrity": "sha512-Zjt7u8wNvDg40rPTGoL+TnfkpuSKjwubsNSFRH1KAVZLcaV4I3AFNHIFbvH7p4F3alEibSbdd90xAgn5Rnfndg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.3.0.tgz", + "integrity": "sha512-2JSxyfpEFWNUB2vKs/T1BvkfyNisMHWph8bLMj8T0uHwmLl/0qfAwQkfwMT6kxLXN9uIum9AEbECXU8er3amIg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2" + "@babel/runtime": "^7.29.7" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -941,17 +942,17 @@ } }, "node_modules/@mui/utils": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.2.0.tgz", - "integrity": "sha512-OsUH5zhlSOM4xmLl53+agug1M1UyWb4zxFxWQCqwKTKUeQPvTENtg3JhrroBD2qpCLKsX5W/DYGERJ4mBUbc8g==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-2HZdHwWJ6eB+7lVGSOHsByGw8jeRulT4g0NZ608Wb8Q57DE2jbNqrWPFuJsvkQQiBiTmlpvQL3i+/62zsiPrkw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2", - "@mui/types": "^9.1.1", + "@babel/runtime": "^7.29.7", + "@mui/types": "^9.3.0", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^19.2.6" + "react-is": "^19.2.8" }, "engines": { "node": ">=14.0.0" @@ -1010,14 +1011,14 @@ } }, "node_modules/@mui/x-internals": { - "version": "9.10.1", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-9.10.1.tgz", - "integrity": "sha512-pKFVzFQHB34TuMG3gOVx+LfIR8VBsi/ZhX/erRX2WOXu+ewEYGvdeK1mW9hLe7e2v3uyUbgT8RNeN0cy10cDjg==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-9.11.0.tgz", + "integrity": "sha512-JjKe9k1+gVWNPwMTZLNSc92eVmoZqP5Xq3Ui6mJkstotUrbWIZC0o9+4AfTR1lYqWwnKmqm+VtLLVCa7MEBXWQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", "@base-ui/utils": "^0.3.1", - "@mui/utils": "^9.2.0", + "@mui/utils": "^9.3.0", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, @@ -1032,6 +1033,45 @@ "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@mui/x-tree-view": { + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-9.11.0.tgz", + "integrity": "sha512-ZJcnlOr3LwQF9n+xa/4Xh0w8wDCY250smbLGmRzK107ClifYSDTR8/IrRQg8yKBwpDYWDQZW85t0voQcverk1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@base-ui/utils": "^0.3.1", + "@mui/utils": "^9.3.0", + "@mui/x-internals": "^9.11.0", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^7.3.0 || ^9.0.0", + "@mui/system": "^7.3.0 || ^9.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, "node_modules/@mui/x-virtualizer": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.6.2.tgz", diff --git a/buildtime/client/material-editor/package.json b/buildtime/client/material-editor/package.json index 52533958c..a3b9fc832 100644 --- a/buildtime/client/material-editor/package.json +++ b/buildtime/client/material-editor/package.json @@ -16,6 +16,7 @@ "@mui/icons-material": "^9.2.0", "@mui/material": "^9.2.0", "@mui/x-data-grid": "^9.10.1", + "@mui/x-tree-view": "^9.11.0", "keycloak-js": "^26.2.4", "ky": "^2.0.2", "react": "^19.2.8", diff --git a/buildtime/client/material-editor/src/App.tsx b/buildtime/client/material-editor/src/App.tsx index 152d901a1..60209b0b2 100644 --- a/buildtime/client/material-editor/src/App.tsx +++ b/buildtime/client/material-editor/src/App.tsx @@ -7,6 +7,7 @@ const Layout = lazy(() => import('./Layout.tsx')) const HomePage = lazy(() => import('./pages/HomePage.tsx')) const LanguagesPage = lazy(() => import('./pages/LanguagesPage.tsx')) const PropertyPage = lazy(() => import('./pages/PropertyPage.tsx')) +const ProductPage = lazy(() => import('./pages/ProductPage.tsx')) const NotFoundPage = lazy(() => import('./pages/NotFoundPage.tsx')) const renderLazyRoute = (Component: ComponentType) => ( @@ -24,6 +25,7 @@ const router = createBrowserRouter([ { index: true, element: renderLazyRoute(HomePage) }, { path: 'language', element: renderLazyRoute(LanguagesPage) }, { path: 'propertypool', element: renderLazyRoute(PropertyPage) }, + { path: 'product', element: renderLazyRoute(ProductPage) }, { path: '*', element: renderLazyRoute(NotFoundPage) }, ], }, diff --git a/buildtime/client/material-editor/src/api/language/productGroup.api.ts b/buildtime/client/material-editor/src/api/language/productGroup.api.ts new file mode 100644 index 000000000..8896afac8 --- /dev/null +++ b/buildtime/client/material-editor/src/api/language/productGroup.api.ts @@ -0,0 +1,22 @@ +import ky from 'ky' +import type { ProductGroup } from '../../data/productGroup' + +export const getProductGroups = async (): Promise => { + return ky.get(`http://localhost:8088/productgroups`).json() +} + +export const getProductGroupById = async (id: number): Promise => { + return ky.get(`http://localhost:8088/productgroup/${id}`).json() +} + +export const createProductGroup = async (productGroup: ProductGroup): Promise => { + return ky.post(`http://localhost:8088/productgroup`, { json: productGroup }).json() +} + +export const updateProductGroup = async (productGroup: ProductGroup): Promise => { + await ky.put(`http://localhost:8088/productgroup`, { json: productGroup }) +} + +export const deleteProductGroup = async (id: number): Promise => { + await ky.delete(`http://localhost:8088/productgroup/${id}`) +} diff --git a/buildtime/client/material-editor/src/components/NavBar.tsx b/buildtime/client/material-editor/src/components/NavBar.tsx index 76894da19..dc859e1c6 100644 --- a/buildtime/client/material-editor/src/components/NavBar.tsx +++ b/buildtime/client/material-editor/src/components/NavBar.tsx @@ -18,7 +18,7 @@ function NavBar() { - + diff --git a/buildtime/client/material-editor/src/components/ProductGroupTreeView.tsx b/buildtime/client/material-editor/src/components/ProductGroupTreeView.tsx new file mode 100644 index 000000000..a2b1ea305 --- /dev/null +++ b/buildtime/client/material-editor/src/components/ProductGroupTreeView.tsx @@ -0,0 +1,27 @@ +import type { ProductGroup } from '../data/productGroup'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; + +type ProductGroupTreeViewProps = { + ProductGroups?: ProductGroup[]; +}; + +export default function ProductGroupTreeView({ ProductGroups = [] }: ProductGroupTreeViewProps) { + + const renderTree = (node: ProductGroup) => ( + + {node.childProductGroups && node.childProductGroups.map((ProductGroup) => ( + renderTree(ProductGroup) + ))} + + ); + + return ( + + {ProductGroups.map((productGroup) => renderTree(productGroup))} + + ); +} diff --git a/buildtime/client/material-editor/src/data/product.ts b/buildtime/client/material-editor/src/data/product.ts index e69de29bb..64ab772f3 100644 --- a/buildtime/client/material-editor/src/data/product.ts +++ b/buildtime/client/material-editor/src/data/product.ts @@ -0,0 +1,9 @@ +export type Product = { + id: number; + label: string; + product_identifier: string; + properties: unknown[]; + products: Product[]; + productStructureNodes: unknown[]; + isEqualPropertiesEqualValues: boolean; +}; diff --git a/buildtime/client/material-editor/src/data/productGroup.ts b/buildtime/client/material-editor/src/data/productGroup.ts index 326093d27..21192e37f 100644 --- a/buildtime/client/material-editor/src/data/productGroup.ts +++ b/buildtime/client/material-editor/src/data/productGroup.ts @@ -7,7 +7,7 @@ export type ProductGroup = { parentGroupId: number | null; isVisible: boolean; path: string; - SubGroups: ProductGroup[]; + childProductGroups: ProductGroup[]; PropertyTypes: PropertyType[]; Products: Product[]; }; \ No newline at end of file diff --git a/buildtime/client/material-editor/src/pages/ProductPage.tsx b/buildtime/client/material-editor/src/pages/ProductPage.tsx new file mode 100644 index 000000000..b1131b5fe --- /dev/null +++ b/buildtime/client/material-editor/src/pages/ProductPage.tsx @@ -0,0 +1,40 @@ +import React, { useEffect, useState } from 'react'; +import ProductGroupTreeView from '../components/ProductGroupTreeView'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import type { ProductGroup } from '../data/productGroup'; +import { getProductGroupById } from '../api/language/productGroup.api'; + +const ProductPage: React.FC = () => { + const [productGroup, setProductGroup] = useState(null); + + const loadProductGroup = React.useCallback(async () => { + const loadedProductGroup = await getProductGroupById(3); // Assuming 3 is the ID for product groups + setProductGroup(loadedProductGroup); + }, []); + + useEffect(() => { + loadProductGroup(); + }, [loadProductGroup]); + + return ( + + + Property Type Management + + + + + + ); + }; + +export default ProductPage; diff --git a/database/dump-simply_cpq_master_data-202608112212.sql b/database/dump-simply_cpq_master_data-202608112212.sql new file mode 100644 index 0000000000000000000000000000000000000000..d59ba24cde8fb4084d822d34fc26969b2f988395 GIT binary patch literal 76932 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 literal 0 HcmV?d00001