diff --git a/package-lock.json b/package-lock.json index c5448e13..68ac87b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,11 @@ "@applitools/eyes-playwright": "^1.23.6", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.12", + "@mui/lab": "^5.0.0-alpha.167", "@mui/material": "^5.15.11", "@mui/x-date-pickers": "^6.19.5", + "@mui/x-tree-view": "^6.17.0", "@types/git-username": "^1.0.5", "@types/leaflet.markercluster": "^1.5.2", "@vitejs/plugin-react-swc": "^3.6.0", @@ -3981,6 +3984,102 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "5.15.12", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.12.tgz", + "integrity": "sha512-3BXiDlOd3AexZoEXa/VqpIpVIvosCzjLHsdMWzKMXbZdnBiJjmb9ECdqfjn5SpTClO49qvkKLhkTqdBH3fSFGw==", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab": { + "version": "5.0.0-alpha.167", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.167.tgz", + "integrity": "sha512-BNQJ7fBBvL68WGVnzAhbtTmabSuJDXaILr9dz/3RNK4TgGXPgWCAr7qtJeUdc4p1t7c4Z1ifG8UwgqD+5hzMNg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.38", + "@mui/system": "^5.15.12", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.12", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": ">=5.15.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab/node_modules/@mui/base": { + "version": "5.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.38.tgz", + "integrity": "sha512-AsjD6Y1X5A1qndxz8xCcR8LDqv31aiwlgWMPxFAX/kCKiIGKlK65yMeVZ62iQr/6LBz+9hSKLiD1i4TZdAHKcQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.12", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "5.15.11", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.11.tgz", @@ -4031,12 +4130,12 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/@mui/private-theming": { - "version": "5.15.11", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.11.tgz", - "integrity": "sha512-jY/696SnSxSzO1u86Thym7ky5T9CgfidU3NFJjguldqK4f3Z5S97amZ6nffg8gTD0HBjY9scB+4ekqDEUmxZOA==", + "version": "5.15.12", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.12.tgz", + "integrity": "sha512-cqoSo9sgA5HE+8vZClbLrq9EkyOnYysooepi5eKaKvJ41lReT2c5wOZAeDDM1+xknrMDos+0mT2zr3sZmUiRRA==", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.11", + "@mui/utils": "^5.15.12", "prop-types": "^15.8.1" }, "engines": { @@ -4093,15 +4192,15 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/@mui/system": { - "version": "5.15.11", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.11.tgz", - "integrity": "sha512-9j35suLFq+MgJo5ktVSHPbkjDLRMBCV17NMBdEQurh6oWyGnLM4uhU4QGZZQ75o0vuhjJghOCA1jkO3+79wKsA==", + "version": "5.15.12", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.12.tgz", + "integrity": "sha512-/pq+GO6yN3X7r3hAwFTrzkAh7K1bTF5r8IzS79B9eyKJg7v6B/t4/zZYMR6OT9qEPtwf6rYN2Utg1e6Z7F1OgQ==", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.11", + "@mui/private-theming": "^5.15.12", "@mui/styled-engine": "^5.15.11", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.11", + "@mui/utils": "^5.15.12", "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -4150,9 +4249,9 @@ } }, "node_modules/@mui/utils": { - "version": "5.15.11", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.11.tgz", - "integrity": "sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==", + "version": "5.15.12", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.12.tgz", + "integrity": "sha512-8SDGCnO2DY9Yy+5bGzu00NZowSDtuyHP4H8gunhHGQoIlhlY2Z3w64wBzAOLpYw/ZhJNzksDTnS/i8qdJvxuow==", "dependencies": { "@babel/runtime": "^7.23.9", "@types/prop-types": "^15.7.11", @@ -4241,6 +4340,35 @@ } } }, + "node_modules/@mui/x-tree-view": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.17.0.tgz", + "integrity": "sha512-09dc2D+Rjg2z8KOaxbUXyPi0aw7fm2jurEtV8Xw48xJ00joLWd5QJm1/v4CarEvaiyhTQzHImNqdgeJW8ZQB6g==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.20", + "@mui/utils": "^5.14.14", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@ndelangen/get-tarball": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@ndelangen/get-tarball/-/get-tarball-3.0.9.tgz", diff --git a/package.json b/package.json index b240b654..7ac821ab 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,11 @@ "@applitools/eyes-playwright": "^1.23.6", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.12", + "@mui/lab": "^5.0.0-alpha.167", "@mui/material": "^5.15.11", "@mui/x-date-pickers": "^6.19.5", + "@mui/x-tree-view": "^6.17.0", "@types/git-username": "^1.0.5", "@types/leaflet.markercluster": "^1.5.2", "@vitejs/plugin-react-swc": "^3.6.0", diff --git a/src/pages/components/CustomTreeView.tsx b/src/pages/components/CustomTreeView.tsx new file mode 100644 index 00000000..f4c5d1b4 --- /dev/null +++ b/src/pages/components/CustomTreeView.tsx @@ -0,0 +1,89 @@ +import { TreeView } from '@mui/x-tree-view/TreeView' +import { TreeItem } from '@mui/x-tree-view/TreeItem' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import ChevronRightIcon from '@mui/icons-material/ChevronRight' + +interface BaseTreeNode { + id: string + name: string +} + +// Generic TreeNode interface with additional properties +interface TreeNode extends BaseTreeNode { + children?: TreeNode[] + // You can now include additional properties from T +} + +// Generic CustomTreeViewProps interface +interface CustomTreeViewProps { + data: T + name: string + id: string +} + +const isPrimitive = (value: unknown) => value !== Object(value) || value === null +// A utility function to transform any object into a TreeNode structure +const objectToTreeNode = >( + obj: T, + key: string = 'root', + nodeId: string = '0', +): TreeNode => { + const children = Object.keys(obj).map((k, index) => { + const value = obj[k] + if (isPrimitive(value)) { + // Handle primitive types directly + return { + id: `${nodeId}-${index}`, + name: `${k}: ${value}`, + } + } else if (Array.isArray(value)) { + // If it's an array, create a node that lists all elements + return { + id: `${nodeId}-${index}`, + name: k, + children: value.map((item, itemIndex) => { + // Handle primitive items in the array directly + if (isPrimitive(item)) { + return { + id: `${nodeId}-${index}-${itemIndex}`, + name: `${item}`, + } + } + // Recursively handle objects in the array + return objectToTreeNode(item, k, `${nodeId}-${index}-${itemIndex}`) + }), + } + } else { + // Recursively handle objects + return objectToTreeNode(value as Record, k, `${nodeId}-${index}`) + } + }) + + return { + id: nodeId, + name: (obj.name || key) as string, + children: children.length ? children : undefined, + } +} + +// Render tree function utilizing the TreeNode interface +const renderTree = (nodes: TreeNode): JSX.Element => ( + + {nodes.children?.map((node) => renderTree(node))} + +) + +// CustomTreeView component using TypeScript +const CustomTreeView = ({ data, name, id }: CustomTreeViewProps) => { + const dataAsTreeNode = objectToTreeNode({ id, name, children: [data] }) + return ( + } + defaultExpandIcon={} + defaultEndIcon={
}> + {renderTree(dataAsTreeNode)} + + ) +} + +export default CustomTreeView diff --git a/src/pages/components/CutomTreeView.stories.tsx b/src/pages/components/CutomTreeView.stories.tsx new file mode 100644 index 00000000..09ab47e4 --- /dev/null +++ b/src/pages/components/CutomTreeView.stories.tsx @@ -0,0 +1,43 @@ +// CustomTreeView.stories.tsx +import type { Meta, StoryObj } from '@storybook/react' +import CustomTreeView from './CustomTreeView' +import '../../shared/shared.css' // Assuming you have some shared styles + +// Example data for the story +const exampleData = { + id: 'root', + name: 'Root Node', + children: [ + { + id: '1', + name: 'Child Node 1', + children: [ + { id: '1-1', name: 'Grandchild Node 1-1' }, + { id: '1-2', name: 'Grandchild Node 1-2' }, + ], + }, + { + id: '2', + name: 'Child Node 2', + }, + ], +} + +const meta: Meta = { + title: 'Components/CustomTreeView', + component: CustomTreeView, + parameters: { + layout: 'centered', + }, + tags: ['tree', 'view', 'react', 'mui'], // Adjust tags as needed +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + data: exampleData, // Using the example data for the default story + }, +} diff --git a/src/pages/components/map-related/MapLayers/BusToolTip.tsx b/src/pages/components/map-related/MapLayers/BusToolTip.tsx index d0efbdee..c9a0abf0 100644 --- a/src/pages/components/map-related/MapLayers/BusToolTip.tsx +++ b/src/pages/components/map-related/MapLayers/BusToolTip.tsx @@ -10,6 +10,7 @@ import { SiriRideWithRelatedPydanticModel } from 'open-bus-stride-client/openapi import { useTranslation } from 'react-i18next' import { Spin } from 'antd' import cn from 'classnames' +import CustomTreeView from '../../CustomTreeView' export type BusToolTipProps = { position: Point; icon: string } @@ -94,11 +95,16 @@ export function BusToolTip({ position, icon }: BusToolTipProps) { {showJson ? t('hide_document') : t('show_document')} {showJson && ( -
-          {JSON.stringify(position, null, 2)}
-          
- {siriRide && JSON.stringify(siriRide, null, 2)} -
+
e.stopPropagation()}> + id={position.point?.id + ''} data={position} name={t('line')} /> + {siriRide?.gtfsRideId && ( + + id={siriRide?.gtfsRideId + ''} + data={siriRide} + name={t('drive_direction')} + /> + )} +
)}
)