diff --git a/package-lock.json b/package-lock.json index a489377e..09d28e28 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", @@ -3980,6 +3983,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", @@ -4030,12 +4129,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": { @@ -4092,15 +4191,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" @@ -4149,9 +4248,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", @@ -4240,6 +4339,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 0f7cab6a..1fd5c630 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,14 @@ "type": "commonjs", "dependencies": { "@applitools/eyes-playwright": "^1.23.6", - "@types/git-username": "^1.0.5", "@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", "antd": "^5.14.2", diff --git a/src/pages/components/CustomTreeView.tsx b/src/pages/components/CustomTreeView.tsx new file mode 100644 index 00000000..38cf29e1 --- /dev/null +++ b/src/pages/components/CustomTreeView.tsx @@ -0,0 +1,91 @@ +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; +} + +// A utility function to transform any object into a TreeNode structure +const objectToTreeNode = ( + obj: any, + key: string = 'root', + nodeId: string = '0' +): TreeNode => { + const isPrimitive = (value: any) => value !== Object(value) || value === null; + + 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, k, `${nodeId}-${index}`); + } + }); + + return { + id: nodeId, + name: obj.name || key, + 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..ea61ab72 --- /dev/null +++ b/src/pages/components/CutomTreeView.stories.tsx @@ -0,0 +1,50 @@ +// CustomTreeView.stories.tsx +import type { Meta, StoryObj } from '@storybook/react'; +import CustomTreeView from './CustomTreeView'; +import '../../shared/shared.css'; // Assuming you have some shared styles + +// Define a base type for your tree nodes, considering the object structure you provided earlier +interface CustomTreeNode { + id: string; + name: string; + children?: CustomTreeNode[]; +} + +// 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.stories.tsx b/src/pages/components/map-related/MapLayers/BusToolTip.stories.tsx index d734b1f9..2fa6ea72 100644 --- a/src/pages/components/map-related/MapLayers/BusToolTip.stories.tsx +++ b/src/pages/components/map-related/MapLayers/BusToolTip.stories.tsx @@ -1,5 +1,6 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { BusToolTip, BusToolTipProps } from './BusToolTip' +import type { Meta, StoryObj } from '@storybook/react'; +import { BusToolTip, BusToolTipProps } from './BusToolTip'; +import { BrowserRouter } from 'react-router-dom'; const meta = { title: 'Components/MapLayers/BusToolTip', @@ -8,11 +9,11 @@ const meta = { layout: 'centered', }, tags: ['map', 'tooltip', 'autodocs'], -} satisfies Meta +} satisfies Meta; -export default meta +export default meta; -type Story = StoryObj +type Story = StoryObj; const defaultArgs: BusToolTipProps = { position: { @@ -48,8 +49,14 @@ const defaultArgs: BusToolTipProps = { } as any, }, icon: '/bus-logos/3.svg', -} +}; export const Default: Story = { args: defaultArgs, -} + // Wrap the component render function with BrowserRouter + render: (args) => ( + + + + ), +}; diff --git a/src/pages/components/map-related/MapLayers/BusToolTip.tsx b/src/pages/components/map-related/MapLayers/BusToolTip.tsx index d0efbdee..f901ec6a 100644 --- a/src/pages/components/map-related/MapLayers/BusToolTip.tsx +++ b/src/pages/components/map-related/MapLayers/BusToolTip.tsx @@ -1,34 +1,35 @@ -import { useEffect, useState } from 'react' -import { Link } from 'react-router-dom' -import { Point } from 'src/pages/realtimeMap' -import { Button } from '@mui/material' -import moment from 'moment-timezone' -import './BusToolTip.scss' +import { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { Point } from 'src/pages/realtimeMap'; +import { Box, Button, Grid } from '@mui/material'; +import moment from 'moment-timezone'; +import './BusToolTip.scss'; -import { getSiriRideWithRelated } from 'src/api/siriService' -import { SiriRideWithRelatedPydanticModel } from 'open-bus-stride-client/openapi/models/SiriRideWithRelatedPydanticModel' -import { useTranslation } from 'react-i18next' -import { Spin } from 'antd' -import cn from 'classnames' +import { getSiriRideWithRelated } from 'src/api/siriService'; +import { SiriRideWithRelatedPydanticModel } from 'open-bus-stride-client/openapi/models/SiriRideWithRelatedPydanticModel'; +import { useTranslation } from 'react-i18next'; +import { Spin } from 'antd'; +import cn from 'classnames'; +import CustomTreeView from '../../CustomTreeView'; -export type BusToolTipProps = { position: Point; icon: string } +export type BusToolTipProps = { position: Point; icon: string }; export function BusToolTip({ position, icon }: BusToolTipProps) { - const [siriRide, setSiriRide] = useState() - const [isLoading, setIsLoading] = useState(false) - const [showJson, setShowJson] = useState(false) - const { t } = useTranslation() + const [siriRide, setSiriRide] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [showJson, setShowJson] = useState(false); + const { t } = useTranslation(); useEffect(() => { - setIsLoading(true) + setIsLoading(true); getSiriRideWithRelated( position.point!.siri_route__id.toString(), position.point!.siri_ride__vehicle_ref.toString(), - position.point!.siri_route__line_ref.toString(), + position.point!.siri_route__line_ref.toString() ) .then((siriRideRes: SiriRideWithRelatedPydanticModel) => setSiriRide(siriRideRes)) - .finally(() => setIsLoading(false)) - }, [position]) + .finally(() => setIsLoading(false)); + }, [position]); return (
@@ -94,12 +95,17 @@ 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')} + /> + )} +
)}
- ) + ); }