From d4c9b1a045d214ca05fb1adaf6ab28cfe444c473 Mon Sep 17 00:00:00 2001 From: cka-y <60586858+cka-y@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:20:44 -0400 Subject: [PATCH] feat: GTFS Schedule Analytics UI (#653) --- web-app/package.json | 15 +- web-app/src/app/App.tsx | 22 +- web-app/src/app/components/Header.tsx | 128 ++- web-app/src/app/constants/Navigation.ts | 2 + web-app/src/app/interface/RemoteConfig.ts | 11 + web-app/src/app/router/Router.tsx | 9 + .../Analytics/GTFSFeatureAnalytics/index.tsx | 225 +++++ .../GTFSFeedAnalytics/DetailPanel.tsx | 199 +++++ .../GTFSFeedAnalyticsTable.tsx | 329 ++++++++ .../Analytics/GTFSFeedAnalytics/index.tsx | 229 +++++ .../Analytics/GTFSNoticeAnalytics/index.tsx | 263 ++++++ .../src/app/screens/Analytics/analytics.css | 32 + web-app/src/app/screens/Analytics/types.ts | 49 ++ web-app/src/app/screens/FAQ.tsx | 7 +- .../src/app/screens/Feed/PreviousDatasets.tsx | 72 +- web-app/src/app/screens/Home.tsx | 7 +- web-app/src/app/store/analytics-reducer.ts | 72 ++ .../src/app/store/gtfs-analytics-selector.ts | 10 + web-app/src/app/store/profile-selectors.ts | 3 + web-app/src/app/store/reducers.ts | 2 + .../src/app/store/saga/gtfs-analytics-saga.ts | 103 +++ web-app/src/app/store/saga/root-saga.ts | 2 + web-app/src/app/utils/analytics.ts | 62 ++ web-app/yarn.lock | 789 +++++++++++++++--- 24 files changed, 2464 insertions(+), 178 deletions(-) create mode 100644 web-app/src/app/screens/Analytics/GTFSFeatureAnalytics/index.tsx create mode 100644 web-app/src/app/screens/Analytics/GTFSFeedAnalytics/DetailPanel.tsx create mode 100644 web-app/src/app/screens/Analytics/GTFSFeedAnalytics/GTFSFeedAnalyticsTable.tsx create mode 100644 web-app/src/app/screens/Analytics/GTFSFeedAnalytics/index.tsx create mode 100644 web-app/src/app/screens/Analytics/GTFSNoticeAnalytics/index.tsx create mode 100644 web-app/src/app/screens/Analytics/analytics.css create mode 100644 web-app/src/app/screens/Analytics/types.ts create mode 100644 web-app/src/app/store/analytics-reducer.ts create mode 100644 web-app/src/app/store/gtfs-analytics-selector.ts create mode 100644 web-app/src/app/store/saga/gtfs-analytics-saga.ts create mode 100644 web-app/src/app/utils/analytics.ts diff --git a/web-app/package.json b/web-app/package.json index 2ba21c217..759f6913b 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -3,20 +3,22 @@ "version": "0.1.0", "private": true, "dependencies": { - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.11.0", + "@emotion/react": "^11.13.0", + "@emotion/styled": "^11.13.0", "@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/free-brands-svg-icons": "^6.4.2", "@fortawesome/free-regular-svg-icons": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^5.14.9", - "@mui/material": "^5.14.9", + "@mui/icons-material": "^5.16.4", + "@mui/material": "^5.16.4", + "@mui/x-date-pickers": "^7.11.0", "@mui/x-tree-view": "^6.17.0", "@reduxjs/toolkit": "^1.9.6", "@turf/center": "^6.5.0", "@types/i18next": "^13.0.0", "@types/leaflet": "^1.9.12", + "axios": "^1.7.2", "country-code-emoji": "^2.3.0", "date-fns": "^2.30.0", "date-fns-tz": "^2.0.0", @@ -26,6 +28,9 @@ "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.5.2", "leaflet": "^1.9.4", + "material-react-table": "^2.13.0", + "mui-datatables": "^4.3.0", + "mui-nested-menu": "^3.4.0", "openapi-fetch": "^0.9.3", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", @@ -38,6 +43,7 @@ "react-redux": "^8.1.3", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", + "recharts": "^2.12.7", "redux-persist": "^6.0.0", "redux-saga": "^1.2.3", "typeface-muli": "^1.1.13", @@ -95,6 +101,7 @@ "@types/cypress": "^1.1.3", "@types/jest": "^29.5.12", "@types/material-ui": "^0.21.12", + "@types/mui-datatables": "^4.3.12", "@types/node": "^20.8.10", "@types/react": "^18.2.25", "@types/react-dom": "^18.2.7", diff --git a/web-app/src/app/App.tsx b/web-app/src/app/App.tsx index 7ec4f7fd8..35162468a 100644 --- a/web-app/src/app/App.tsx +++ b/web-app/src/app/App.tsx @@ -11,6 +11,8 @@ import i18n from '../i18n'; import { Suspense, useEffect, useState } from 'react'; import { I18nextProvider } from 'react-i18next'; import { app } from '../firebase'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; function App(): React.ReactElement { require('typeface-muli'); // Load font @@ -33,15 +35,17 @@ function App(): React.ReactElement { -
- - -
- {isAppReady ? : null} - - -
-
+ +
+ + +
+ {isAppReady ? : null} + + +
+
diff --git a/web-app/src/app/components/Header.tsx b/web-app/src/app/components/Header.tsx index d63921864..b66312274 100644 --- a/web-app/src/app/components/Header.tsx +++ b/web-app/src/app/components/Header.tsx @@ -31,16 +31,18 @@ import { import type NavigationItem from '../interface/Navigation'; import { useNavigate } from 'react-router-dom'; import { useSelector } from 'react-redux'; -import { selectIsAuthenticated } from '../store/selectors'; +import { selectIsAuthenticated, selectUserEmail } from '../store/selectors'; import LogoutConfirmModal from './LogoutConfirmModal'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import { TreeView } from '@mui/x-tree-view/TreeView'; import { TreeItem } from '@mui/x-tree-view/TreeItem'; -import { OpenInNew } from '@mui/icons-material'; +import { BikeScooterOutlined, OpenInNew } from '@mui/icons-material'; import '../styles/Header.css'; import { useRemoteConfig } from '../context/RemoteConfigProvider'; import i18n from '../../i18n'; +import { NestedMenuItem } from 'mui-nested-menu'; +import DirectionsBusIcon from '@mui/icons-material/DirectionsBus'; const drawerWidth = 240; const websiteTile = 'Mobility Database'; @@ -95,6 +97,38 @@ const DrawerContent: React.FC<{ ))} + } + defaultExpandIcon={} + sx={{ textAlign: 'left' }} + > + + { + onNavigationClick('/metrics/gtfs/feeds'); + }} + /> + { + onNavigationClick('/metrics/gtfs/notices'); + }} + /> + { + onNavigationClick('/metrics/gtfs/features'); + }} + /> + + {isAuthenticated ? ( } @@ -166,6 +200,7 @@ export default function DrawerAppBar(): React.ReactElement { const navigateTo = useNavigate(); const isAuthenticated = useSelector(selectIsAuthenticated); + const userEmail = useSelector(selectUserEmail); const handleDrawerToggle = (): void => { setMobileOpen((prevState) => !prevState); @@ -199,7 +234,7 @@ export default function DrawerAppBar(): React.ReactElement { setAnchorEl(null); }; - const handleMenuItemClick = (item: NavigationItem): void => { + const handleMenuItemClick = (item: NavigationItem | string): void => { handleMenuClose(); handleNavigation(item); }; @@ -260,6 +295,87 @@ export default function DrawerAppBar(): React.ReactElement { {item.title} ))} + {/* Allow users with mobilitydata.org email to access metrics */} + {config.enableMetrics || + (userEmail?.endsWith('mobilitydata.org') === true && ( + <> + + + } + > + { + handleMenuItemClick('/metrics/gtfs/feeds'); + }} + > + Feeds + + { + handleMenuItemClick('/metrics/gtfs/notices'); + }} + > + Notices + + { + handleMenuItemClick('/metrics/gtfs/features'); + }} + > + Features + + + } + > + { + handleMenuItemClick('/metrics/gbfs/feeds'); + }} + > + Feeds + + { + handleMenuItemClick('/metrics/gbfs/notices'); + }} + > + Notices + + { + handleMenuItemClick('/metrics/gbfs/versions'); + }} + > + Versions + + + + + ))} + {isAuthenticated ? ( <> { void i18n.changeLanguage(lang.target.value); }} + variant='standard' > EN FR diff --git a/web-app/src/app/constants/Navigation.ts b/web-app/src/app/constants/Navigation.ts index c386ea7aa..dfc70764d 100644 --- a/web-app/src/app/constants/Navigation.ts +++ b/web-app/src/app/constants/Navigation.ts @@ -16,6 +16,8 @@ export const MOBILITY_DATA_LINKS = { github: 'https://github.com/MobilityData/mobility-database-catalogs', }; +export const WEB_VALIDATOR_LINK = 'https://gtfs-validator.mobilitydata.org'; + export function buildNavigationItems( featureFlags: RemoteConfigValues, ): NavigationItem[] { diff --git a/web-app/src/app/interface/RemoteConfig.ts b/web-app/src/app/interface/RemoteConfig.ts index 370c2c02b..21111ce14 100644 --- a/web-app/src/app/interface/RemoteConfig.ts +++ b/web-app/src/app/interface/RemoteConfig.ts @@ -13,6 +13,14 @@ export interface RemoteConfigValues extends FirebaseDefaultConfig { * false: renders the legacy feed submission page based in the Contribute.tsx */ enableFeedSubmissionStepper: boolean; + /** Enable Metrics view + * Values: + * true: renders the metrics view + * false: hides the metrics view + */ + enableMetrics: boolean; + /** GTFS metrics' bucket endpoint */ + gtfsMetricsBucketEndpoint: string; } // Add default values for remote config here @@ -21,6 +29,9 @@ export const defaultRemoteConfigValues: RemoteConfigValues = { enableFeedsPage: false, enableLanguageToggle: false, enableFeedSubmissionStepper: false, + enableMetrics: false, + gtfsMetricsBucketEndpoint: + 'https://storage.googleapis.com/mobilitydata-gtfs-analytics-dev', }; remoteConfig.defaultConfig = defaultRemoteConfigValues; diff --git a/web-app/src/app/router/Router.tsx b/web-app/src/app/router/Router.tsx index 3ce201191..25d059ab7 100644 --- a/web-app/src/app/router/Router.tsx +++ b/web-app/src/app/router/Router.tsx @@ -27,6 +27,9 @@ import { logout } from '../store/profile-reducer'; import FeedSubmission from '../screens/FeedSubmission'; import FeedSubmissionFAQ from '../screens/FeedSubmissionFAQ'; import FeedSubmitted from '../screens/FeedSubmitted'; +import GTFSFeedAnalytics from '../screens/Analytics/GTFSFeedAnalytics'; +import GTFSNoticeAnalytics from '../screens/Analytics/GTFSNoticeAnalytics'; +import GTFSFeatureAnalytics from '../screens/Analytics/GTFSFeatureAnalytics'; export const AppRouter: React.FC = () => { const navigateTo = useNavigate(); @@ -89,6 +92,12 @@ export const AppRouter: React.FC = () => { } /> } /> } /> + + } /> + } /> + } /> + } /> + ); }; diff --git a/web-app/src/app/screens/Analytics/GTFSFeatureAnalytics/index.tsx b/web-app/src/app/screens/Analytics/GTFSFeatureAnalytics/index.tsx new file mode 100644 index 000000000..327a709de --- /dev/null +++ b/web-app/src/app/screens/Analytics/GTFSFeatureAnalytics/index.tsx @@ -0,0 +1,225 @@ +import { useState, useEffect, useMemo } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { + MaterialReactTable, + type MRT_ColumnDef, + type MRT_Cell, + useMaterialReactTable, +} from 'material-react-table'; +import { + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + Brush, + Line, + LineChart, +} from 'recharts'; +import Box from '@mui/material/Box'; +import { Typography, Button } from '@mui/material'; +import * as React from 'react'; +import { useTheme } from '@mui/material/styles'; +import { InfoOutlined, ListAltOutlined } from '@mui/icons-material'; +import { featureGroups, getGroupColor } from '../../../utils/analytics'; +import { type FeatureMetrics } from '../types'; +import { useRemoteConfig } from '../../../context/RemoteConfigProvider'; + +export default function GTFSFeatureAnalytics(): React.ReactElement { + const navigateTo = useNavigate(); + const { search } = useLocation(); + const params = new URLSearchParams(search); + const featureName = params.get('featureName'); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const { config } = useRemoteConfig(); + + useEffect(() => { + const fetchData = async (): Promise => { + try { + const response = await fetch( + `${config.gtfsMetricsBucketEndpoint}/features_metrics.json`, + ); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const fetchedData = await response.json(); + const dataWithGroups = fetchedData.map((feature: FeatureMetrics) => ({ + ...feature, + latest_feed_count: feature.feeds_count.slice(-1)[0], + feature_group: Object.keys(featureGroups).find((group) => + featureGroups[group].includes(feature.feature), + ), + })); + setData(dataWithGroups); + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + void fetchData(); + }, []); + + const columns = useMemo>>( + () => [ + { + accessorKey: 'feature', + header: 'Feature Name', + size: 300, + enableClickToCopy: true, + }, + { + accessorKey: 'feature_group', + header: 'Feature Group', + size: 200, + filterVariant: 'multi-select', + filterSelectOptions: Object.keys(featureGroups), + Cell: ({ cell }: { cell: MRT_Cell }) => { + const group = cell.getValue(); + return group == null ? null : ( + + {group} + + ); + }, + }, + { + accessorKey: 'latest_feed_count', + header: 'Number of Feeds', + size: 150, + filterVariant: 'range-slider', + muiFilterSliderProps: { + marks: true, + max: data.reduce( + (max, feature) => Math.max(max, feature.latest_feed_count), + 0, + ), + min: 0, + step: 5, + }, + }, + ], + [data], + ); + + const initialFilters = + featureName != null + ? [ + { + id: 'feature', + value: featureName, + }, + ] + : []; + + const table = useMaterialReactTable({ + columns, + data, + initialState: { + showColumnFilters: true, + columnPinning: { left: ['mrt-row-expand', 'feature'] }, + density: 'compact', + sorting: [{ id: 'feature', desc: false }], + columnFilters: initialFilters, + expanded: initialFilters.length > 0 ? true : {}, + }, + enableStickyHeader: true, + enableStickyFooter: true, + muiTableContainerProps: { sx: { maxHeight: '70vh' } }, + renderDetailPanel: ({ row }) => { + const theme = useTheme(); + const metrics = row.original; + + const chartData = metrics.computed_on.map((date, index) => ({ + date: new Date(date).toLocaleDateString('en-CA', { + timeZone: 'UTC', + }), + feeds: metrics.feeds_count[index], + })); + const domain = [ + new Date(metrics.computed_on[0]).getTime(), + new Date().getTime(), + ]; + + return ( + + + Monthly Feature Metrics + + + + + + + + + + + + + + + + + This graph shows the monthly feed metrics, including the count + of feeds associated with each feature over time. + + + + + + ); + }, + }); + + if (loading) { + return
Loading...
; + } + + return ( + + + Features Analytics{' '} + + + + ); +} diff --git a/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/DetailPanel.tsx b/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/DetailPanel.tsx new file mode 100644 index 000000000..bf0a66774 --- /dev/null +++ b/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/DetailPanel.tsx @@ -0,0 +1,199 @@ +import React from 'react'; +import { Box, Typography, Grid } from '@mui/material'; +import { format } from 'date-fns'; +import { useTheme } from '@mui/material/styles'; +import { + Brush, + CartesianGrid, + Legend, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { + LocationOn, + Business, + TrendingUp, + InfoOutlined, + Key, + CalendarToday, +} from '@mui/icons-material'; +import { type GTFSFeedMetrics } from '../types'; +import { getLocationName } from '../../../services/feeds/utils'; + +interface RowData { + original: GTFSFeedMetrics; +} + +interface RenderDetailPanelProps { + row: RowData; +} + +const getTrendDescription = (current: number, previous: number): string => { + if (current > previous) { + return 'Increase'; + } else if (current < previous) { + return 'Decrease'; + } else { + return 'No Change'; + } +}; + +const DetailPanel: React.FC = ({ row }) => { + const theme = useTheme(); + const { metrics, locations, provider } = row.original; + + if (metrics == null) { + return
No metrics available
; + } + + const chartData = metrics.computed_on.map((date, index) => { + const utcDate = new Date(date).toLocaleDateString('en-CA', { + timeZone: 'UTC', + }); // Converts the date to UTC + + return { + date: utcDate, + errors: metrics.errors_count[index], + warnings: metrics.warnings_count[index], + infos: metrics.infos_count[index], + }; + }); + + const domain = [ + new Date(metrics.computed_on[0]).getTime(), + new Date().getTime(), + ]; + + const lastErrors = metrics.errors_count.slice(-2); + const lastWarnings = metrics.warnings_count.slice(-2); + const lastInfos = metrics.infos_count.slice(-2); + + return ( + + + + + Monthly Feed Validation Metrics + + + + + + + + + + + + + + + + + + + Feed Details + + + + Feed ID:{' '} + {row.original.feed_id} + + + + Added On:{' '} + {format(new Date(row.original.created_on), 'yyyy-MM-dd')} + + + + Locations: + {getLocationName(locations)} + + + + Provider: {provider} + + + + Trends: + +
+ + Errors: {getTrendDescription(lastErrors[1], lastErrors[0])} + + + Warnings:{' '} + {getTrendDescription(lastWarnings[1], lastWarnings[0])} + + + Infos: {getTrendDescription(lastInfos[1], lastInfos[0])} + +
+
+ + {' '} + Visit the{' '} + feed's page{' '} + page for more information. + +
+
+
+
+
+ ); +}; + +export default DetailPanel; diff --git a/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/GTFSFeedAnalyticsTable.tsx b/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/GTFSFeedAnalyticsTable.tsx new file mode 100644 index 000000000..bc92ee715 --- /dev/null +++ b/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/GTFSFeedAnalyticsTable.tsx @@ -0,0 +1,329 @@ +import React, { useMemo } from 'react'; +import { type MRT_Cell, type MRT_ColumnDef } from 'material-react-table'; +import { format } from 'date-fns'; +import { type GTFSFeedMetrics } from '../types'; +import { groupFeatures, getGroupColor } from '../../../utils/analytics'; +import { useNavigate } from 'react-router-dom'; +import { Box, Stack, Tooltip } from '@mui/material'; +import { OpenInNew } from '@mui/icons-material'; + +/** + * Returns the columns for the feed analytics table. + * @param uniqueErrors list of unique errors + * @param uniqueWarnings list of unique warnings + * @param uniqueInfos list of unique infos + * @param uniqueFeatures list of unique features + * @param avgErrors average number of errors + * @param avgWarnings average number of warnings + * @param avgInfos average number of infos + * @returns the columns for the feed analytics table + */ +export const useTableColumns = ( + uniqueErrors: string[], + uniqueWarnings: string[], + uniqueInfos: string[], + uniqueFeatures: string[], + avgErrors: number, + avgWarnings: number, + avgInfos: number, +): Array> => { + const navigate = useNavigate(); + + return useMemo>>( + () => [ + { + accessorKey: 'feed_id', + header: 'Feed ID', + enableColumnPinning: true, + enableHiding: false, + size: 200, + Cell: ({ + cell, + renderedCellValue, + }: { + cell: MRT_Cell; + renderedCellValue: React.ReactNode; + }) => ( + ()} page in new tab`} + placement='top-start' + > +
{ + const url = `/feeds/${cell.getValue()}`; + window.open(url, '_blank'); + }} + > + {renderedCellValue}{' '} + +
+
+ ), + }, + { + accessorKey: 'created_on', + header: 'Created On', + Cell: ({ cell }) => + format(new Date(cell.getValue()), 'yyyy-MM-dd'), + filterVariant: 'date-range', + size: 150, + }, + { + accessorKey: 'locations_string', + header: 'Locations', + size: 100, + }, + { + accessorKey: 'provider', + header: 'Provider', + Cell: ({ + cell, + renderedCellValue, + }: { + cell: MRT_Cell; + renderedCellValue: React.ReactNode; + }) => ( + + {renderedCellValue} + + ), + size: 100, + }, + { + accessorKey: 'notices.errors', + header: 'Errors', + enableSorting: false, + Cell: ({ cell }: { cell: MRT_Cell }) => ( +
+ {cell.getValue()?.map((error, index) => ( +
{ + navigate(`/metrics/gtfs/notices?noticeCode=${error}`); + }} + > + {error} +
+ ))} +
+ ), + filterVariant: 'multi-select', + filterSelectOptions: uniqueErrors, + size: 150, + Header: ( + + Notice Severity : + + ERROR + + + ), + Footer: () => ( + + Average Number of Errors: + {avgErrors} + + ), + }, + { + accessorKey: 'notices.warnings', + header: 'Warnings', + enableSorting: false, + Cell: ({ cell }: { cell: MRT_Cell }) => ( +
+ {cell.getValue()?.map((warning, index) => ( +
{ + navigate(`/metrics/gtfs/notices?noticeCode=${warning}`); + }} + > + {warning} +
+ ))} +
+ ), + filterVariant: 'multi-select', + filterSelectOptions: uniqueWarnings, + size: 150, + Header: ( + + Notice Severity : + + WARNING + + + ), + Footer: () => ( + + Average Number of Warnings: + {avgWarnings} + + ), + }, + { + accessorKey: 'notices.infos', + header: 'Infos', + enableSorting: false, + Header: ( + + Notice Severity : + + INFO + + + ), + Cell: ({ cell }: { cell: MRT_Cell }) => ( +
+ {cell.getValue()?.map((info, index) => ( +
{ + navigate(`/metrics/gtfs/notices?noticeCode=${info}`); + }} + > + {info} +
+ ))} +
+ ), + filterVariant: 'multi-select', + filterSelectOptions: uniqueInfos, + size: 150, + Footer: () => ( + + Average Number of Infos: + {avgInfos} + + ), + }, + { + accessorKey: 'features', + header: 'Features', + filterFn: (value, _, filterValue) => { + const originalValue = value.original; + const features = originalValue.features; + return features.some((feature) => { + return feature.toLowerCase().includes(filterValue.toLowerCase()); + }); + }, + enableSorting: false, + Cell: ({ cell }: { cell: MRT_Cell }) => { + const { groupedFeatures, otherFeatures } = groupFeatures( + cell.getValue(), + ); + return ( +
+ {Object.entries(groupedFeatures).map( + ([group, features], index) => ( +
+
+ {group}: +
+ {features.map((feature, index) => ( +
{ + navigate( + `/metrics/gtfs/features?featureName=${feature}`, + ); + }} + > + {feature} +
+ ))} +
+ ), + )} + {otherFeatures.length > 0 && ( +
+
+ Empty Group: +
+ {otherFeatures.map((feature, index) => ( +
{ + navigate( + `/metrics/gtfs/features?featureName=${feature}`, + ); + }} + > + {feature} +
+ ))} +
+ )} +
+ ); + }, + size: 300, + }, + { + accessorKey: 'dataset_id', + header: 'Dataset ID', + size: 200, + }, + ], + [ + navigate, + uniqueErrors, + uniqueWarnings, + uniqueInfos, + uniqueFeatures, + avgErrors, + avgWarnings, + avgInfos, + ], + ); +}; diff --git a/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/index.tsx b/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/index.tsx new file mode 100644 index 000000000..e091390c7 --- /dev/null +++ b/web-app/src/app/screens/Analytics/GTFSFeedAnalytics/index.tsx @@ -0,0 +1,229 @@ +import React, { useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { + MaterialReactTable, + useMaterialReactTable, +} from 'material-react-table'; +import { + Box, + FormControl, + InputLabel, + MenuItem, + Select, + type SelectChangeEvent, + Typography, +} from '@mui/material'; + +import '../analytics.css'; +import { useLocation } from 'react-router-dom'; +import { + fetchAvailableFilesStart, + selectFile, +} from '../../../store/analytics-reducer'; +import { + selectGTFSFeedMetrics, + selectGTFSAnalyticsStatus, + selectGTFSAnalyticsError, +} from '../../../store/gtfs-analytics-selector'; +import { useTableColumns } from './GTFSFeedAnalyticsTable'; +import DetailPanel from './DetailPanel'; +import { type RootState } from '../../../store/store'; +import { type AnalyticsFile } from '../types'; +import { useRemoteConfig } from '../../../context/RemoteConfigProvider'; + +let globalAnalyticsBucketEndpoint: string | undefined; +export const getAnalyticsBucketEndpoint = (): string | undefined => + globalAnalyticsBucketEndpoint; + +export default function GTFSFeedAnalytics(): React.ReactElement { + const { search } = useLocation(); + const params = new URLSearchParams(search); + const { config } = useRemoteConfig(); + + const severity = params.get('severity'); + const noticeCode = params.get('noticeCode'); + const featureName = params.get('featureName'); + + const dispatch = useDispatch(); + const data = useSelector(selectGTFSFeedMetrics); + const status = useSelector(selectGTFSAnalyticsStatus); + const error = useSelector(selectGTFSAnalyticsError); + + const availableFiles = useSelector( + (state: RootState) => state.gtfsAnalytics.availableFiles, + ); + const selectedFile = useSelector( + (state: RootState) => state.gtfsAnalytics.selectedFile, + ); + + const getFileDisplayKey = (file: AnalyticsFile): JSX.Element => { + const dateString = file.file_name.split('_')[1]; // Extracting the year and month + const date = new Date(dateString.replace('.json', '')); // Creating a date object + const formattedDate = date.toLocaleDateString('en-CA', { + year: 'numeric', + month: 'long', + day: 'numeric', + timeZone: 'UTC', + }); + + return ( + + {formattedDate} + + ); + }; + + React.useEffect(() => { + globalAnalyticsBucketEndpoint = config.gtfsMetricsBucketEndpoint; + dispatch(fetchAvailableFilesStart()); + }, [dispatch, config.gtfsMetricsBucketEndpoint]); + + const handleFileChange = (event: SelectChangeEvent): void => { + const fileName = event.target.value as string; + dispatch(selectFile(fileName)); + }; + + const uniqueErrors = useMemo(() => { + const errors = data.flatMap((item) => item.notices?.errors); + return Array.from(new Set(errors)).sort(); + }, [data]); + + const uniqueWarnings = useMemo(() => { + const warnings = data.flatMap((item) => item.notices?.warnings); + return Array.from(new Set(warnings)).sort(); + }, [data]); + + const uniqueInfos = useMemo(() => { + const infos = data.flatMap((item) => item.notices?.infos); + return Array.from(new Set(infos)).sort(); + }, [data]); + + const uniqueFeatures = useMemo(() => { + const features = data.flatMap((item) => item?.features); + return Array.from(new Set(features)).sort(); + }, [data]); + + const avgErrors = useMemo(() => { + const totalErrors = data.reduce( + (acc, item) => acc + item.notices?.errors.length, + 0, + ); + return Math.round(totalErrors / data.length); + }, [data]); + + const avgWarnings = useMemo(() => { + const totalWarnings = data.reduce( + (acc, item) => acc + item.notices?.warnings.length, + 0, + ); + return Math.round(totalWarnings / data.length); + }, [data]); + + const avgInfos = useMemo(() => { + const totalInfos = data.reduce( + (acc, item) => acc + item.notices?.infos.length, + 0, + ); + return Math.round(totalInfos / data.length); + }, [data]); + + const initialFilters = useMemo(() => { + const filters = []; + if (featureName != null) { + filters.push({ + id: 'features', + value: featureName, + }); + } + if (severity != null && noticeCode != null) { + const id = + severity === 'ERROR' + ? 'notices.errors' + : severity === 'WARNING' + ? 'notices.warnings' + : severity === 'INFO' + ? 'notices.infos' + : undefined; + if (id != null) { + filters.push({ + id, + value: [noticeCode], + }); + } + } + return filters; + }, [severity, noticeCode, featureName]); + + const columns = useTableColumns( + uniqueErrors, + uniqueWarnings, + uniqueInfos, + uniqueFeatures, + avgErrors, + avgWarnings, + avgInfos, + ); + + const table = useMaterialReactTable({ + columns, + data, + initialState: { + showColumnFilters: initialFilters.length > 0, + columnPinning: { left: ['mrt-row-expand', 'feed_id'] }, + density: 'compact', + sorting: [{ id: 'feed_id', desc: false }], + columnFilters: initialFilters, + columnVisibility: { + created_on: false, + dataset_id: false, + locations_string: false, + provider: false, + }, + }, + enableStickyHeader: true, + enableStickyFooter: true, + muiTableContainerProps: { sx: { maxHeight: '70vh' } }, + renderDetailPanel: ({ row }) => , + renderTopToolbarCustomActions: ({ table }) => ( + + + Analytics Compute Date + + + + ), + }); + + // TODO improve this code + if (status === 'loading') { + return
Loading...
; + } + + // TODO improve this code + if (status === 'failed') { + return
Error: {error}
; + } + + return ( + + + Feeds Analytics{' '} + + + + + ); +} diff --git a/web-app/src/app/screens/Analytics/GTFSNoticeAnalytics/index.tsx b/web-app/src/app/screens/Analytics/GTFSNoticeAnalytics/index.tsx new file mode 100644 index 000000000..2c3f004aa --- /dev/null +++ b/web-app/src/app/screens/Analytics/GTFSNoticeAnalytics/index.tsx @@ -0,0 +1,263 @@ +import { useState, useEffect, useMemo } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +import { + MaterialReactTable, + useMaterialReactTable, + type MRT_ColumnDef, + type MRT_Cell, +} from 'material-react-table'; +import { + XAxis, + YAxis, + CartesianGrid, + Legend, + ResponsiveContainer, + Brush, + Line, + LineChart, + Tooltip, +} from 'recharts'; +import Box from '@mui/material/Box'; +import MUITooltip from '@mui/material/Tooltip'; + +import { Typography, Button, IconButton } from '@mui/material'; +import * as React from 'react'; +import { useTheme } from '@mui/material/styles'; +import { InfoOutlined, ListAltOutlined } from '@mui/icons-material'; +import { type NoticeMetrics } from '../types'; +import { WEB_VALIDATOR_LINK } from '../../../constants/Navigation'; +import { useRemoteConfig } from '../../../context/RemoteConfigProvider'; + +export default function GTFSNoticeAnalytics(): React.ReactElement { + const navigateTo = useNavigate(); + const { search } = useLocation(); + const params = new URLSearchParams(search); + const noticeCode = params.get('noticeCode'); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const { config } = useRemoteConfig(); + + useEffect(() => { + const fetchData = async (): Promise => { + try { + const response = await fetch( + `${config.gtfsMetricsBucketEndpoint}/notices_metrics.json`, + ); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const fetchedData = await response.json(); + const dataWLatestCount = fetchedData.map((notice: NoticeMetrics) => ({ + ...notice, + latest_feed_count: notice.feeds_count.slice(-1)[0], + })); + setData(dataWLatestCount); + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + void fetchData(); + }, []); + + const columns = useMemo>>( + () => [ + { + accessorKey: 'notice', + header: 'Notice', + size: 300, + Cell: ({ + cell, + renderedCellValue, + }: { + cell: MRT_Cell; + renderedCellValue: React.ReactNode; + }) => { + return ( +
+ {renderedCellValue} + ()} definition`} + arrow + > + { + window.open( + `${WEB_VALIDATOR_LINK}/rules.html#${cell.getValue()}-rule`, + '_blank', + ); + }} + > + + + +
+ ); + }, + }, + { + accessorKey: 'severity', + header: 'Severity', + size: 150, + filterVariant: 'multi-select', + filterSelectOptions: ['ERROR', 'WARNING', 'INFO'], + Cell: ({ + cell, + renderedCellValue, + }: { + cell: MRT_Cell; + renderedCellValue: React.ReactNode; + }) => { + const severity = cell.getValue(); + const background = + severity === 'ERROR' + ? '#d54402' + : severity === 'WARNING' + ? '#fdba06' + : '#9ae095'; + return ( + + {renderedCellValue} + + ); + }, + }, + { + accessorKey: 'latest_feed_count', + header: 'Number of Feeds', + size: 150, + filterVariant: 'range-slider', + muiFilterSliderProps: { + marks: true, + max: data.reduce( + (max, notice) => Math.max(max, notice.feeds_count.slice(-1)[0]), + 0, + ), + min: 0, + step: 5, + }, + }, + ], + [data], + ); + + const initialFilters = + noticeCode != null + ? [ + { + id: 'notice', + value: noticeCode, + }, + ] + : []; + + const table = useMaterialReactTable({ + columns, + data, + initialState: { + showColumnFilters: true, + columnPinning: { left: ['mrt-row-expand', 'notice'] }, + density: 'compact', + sorting: [{ id: 'notice', desc: false }], + columnFilters: initialFilters, + expanded: initialFilters.length > 0 ? true : {}, + }, + enableStickyHeader: true, + enableStickyFooter: true, + muiTableContainerProps: { sx: { maxHeight: '70vh' } }, + renderDetailPanel: ({ row }) => { + const theme = useTheme(); + const metrics = row.original; + + const chartData = metrics.computed_on.map((date, index) => ({ + date: new Date(date).toLocaleDateString('en-CA', { timeZone: 'UTC' }), + feeds: metrics.feeds_count[index], + })); + const domain = [ + new Date(metrics.computed_on[0]).getTime(), + new Date().getTime(), + ]; + + return ( + + + Monthly Notice Metrics + + + + + + + + + + + + + + + + + This graph shows the monthly feed validation metrics, including + the count of feeds associated with each notice over time. + + + + + + ); + }, + }); + + if (loading) { + return
Loading...
; + } + + return ( + + + Notices Analytics{' '} + + + + ); +} diff --git a/web-app/src/app/screens/Analytics/analytics.css b/web-app/src/app/screens/Analytics/analytics.css new file mode 100644 index 000000000..9a32e6e69 --- /dev/null +++ b/web-app/src/app/screens/Analytics/analytics.css @@ -0,0 +1,32 @@ +.navigable-list-item:hover { + text-decoration: underline; + color: #000; +} + +.navigable-list-item{ + cursor: pointer; +} + +.notice-severity-label { + border-radius: 5px; + padding: 5px; + margin-left: 5px; + margin-bottom: 2px; + width: fit-content; + +} + +.notice-severity-label.notice-severity-error { + background-color: #d54402; + color: white; +} + +.notice-severity-label.notice-severity-warning { + background-color: #f3c280; + color: black; +} + +.notice-severity-label.notice-severity-info { + background-color: #badfb7; + color: black; +} diff --git a/web-app/src/app/screens/Analytics/types.ts b/web-app/src/app/screens/Analytics/types.ts new file mode 100644 index 000000000..3588b1ca9 --- /dev/null +++ b/web-app/src/app/screens/Analytics/types.ts @@ -0,0 +1,49 @@ +import { type EntityLocations } from '../../services/feeds/utils'; + +export interface Notices { + errors: string[]; + warnings: string[]; + infos: string[]; +} + +export interface GTFSFeedMetrics { + feed_id: string; + dataset_id: string; + notices: Notices; + features: string[]; + created_on: Date; + last_modified: Date; + locations: EntityLocations; + locations_string: string; + provider: string; + metrics?: Metrics; +} + +export interface Metrics { + feed_id: string; + computed_on: Date[]; + errors_count: number[]; + warnings_count: number[]; + infos_count: number[]; +} + +export interface NoticeMetrics { + notice: string; + severity: string; + computed_on: Date[]; + feeds_count: number[]; + latest_feed_count: number; +} + +export interface FeatureMetrics { + feature: string; + computed_on: Date[]; + feeds_count: number[]; + latest_feed_count: number; + feature_group?: string; // Add a property to handle feature grouping +} + +export interface AnalyticsFile { + file_name: string; + created_on: Date; +} diff --git a/web-app/src/app/screens/FAQ.tsx b/web-app/src/app/screens/FAQ.tsx index 1bc65aea0..c3c527dbe 100644 --- a/web-app/src/app/screens/FAQ.tsx +++ b/web-app/src/app/screens/FAQ.tsx @@ -5,6 +5,7 @@ import Container from '@mui/material/Container'; import '../styles/SignUp.css'; import '../styles/FAQ.css'; import { Typography } from '@mui/material'; +import { WEB_VALIDATOR_LINK } from '../constants/Navigation'; export default function FAQ(): React.ReactElement { return ( @@ -40,11 +41,7 @@ export default function FAQ(): React.ReactElement { comprehensive resource for ensuring your data is discoverable and for scraping the data you need. The community regularly adds and updates feeds using Github. The Mobility Database integrates with{' '} - + the Canonical GTFS Schedule Validator {' '} to display data quality information about each feed. diff --git a/web-app/src/app/screens/Feed/PreviousDatasets.tsx b/web-app/src/app/screens/Feed/PreviousDatasets.tsx index 750aa6529..c09b9127c 100644 --- a/web-app/src/app/screens/Feed/PreviousDatasets.tsx +++ b/web-app/src/app/screens/Feed/PreviousDatasets.tsx @@ -20,6 +20,7 @@ import { } from '@mui/icons-material'; import { type paths } from '../../services/feeds/types'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import { WEB_VALIDATOR_LINK } from '../../constants/Navigation'; export interface PreviousDatasetsProps { datasets: @@ -169,7 +170,7 @@ export default function PreviousDatasets({ endIcon={} > )} - {dataset.validation_report != null && - dataset.validation_report !== undefined && ( - <> - - + - - )} + JSON Version + + + + )} ))} diff --git a/web-app/src/app/screens/Home.tsx b/web-app/src/app/screens/Home.tsx index 590def0a3..ceff8d7e4 100644 --- a/web-app/src/app/screens/Home.tsx +++ b/web-app/src/app/screens/Home.tsx @@ -14,6 +14,7 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import LegacyHome from './LegacyHome'; import { useRemoteConfig } from '../context/RemoteConfigProvider'; +import { WEB_VALIDATOR_LINK } from '../constants/Navigation'; interface ActionBoxProps { IconComponent: React.ElementType; @@ -220,11 +221,7 @@ function Component(): React.ReactElement { The Mobility Database is a directory of 2000+ mobility feeds across the world. It has over 250 updated feeds previously unavailable on TransitFeeds (OpenMobilityData) and shares data quality reports from{' '} - + the Canonical GTFS Schedule Validator . diff --git a/web-app/src/app/store/analytics-reducer.ts b/web-app/src/app/store/analytics-reducer.ts new file mode 100644 index 000000000..b2083b07f --- /dev/null +++ b/web-app/src/app/store/analytics-reducer.ts @@ -0,0 +1,72 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; +import { + type NoticeMetrics, + type FeatureMetrics, + type GTFSFeedMetrics, + type AnalyticsFile, +} from '../screens/Analytics/types'; + +interface GTFSAnalyticsState { + feedMetrics: GTFSFeedMetrics[]; + historicalMetrics: Map; + noticeMetrics: NoticeMetrics[]; + featuresMetrics: FeatureMetrics[]; + status: 'loading' | 'loaded' | 'failed'; + error?: string; + availableFiles: AnalyticsFile[]; + selectedFile?: string; +} + +const initialState: GTFSAnalyticsState = { + feedMetrics: [], + historicalMetrics: new Map(), + noticeMetrics: [], + featuresMetrics: [], + status: 'loading', + error: undefined, + availableFiles: [], + selectedFile: undefined, +}; + +const GTFSAnalyticsSlice = createSlice({ + name: 'gtfsAnalytics', + initialState, + reducers: { + fetchDataStart(state) { + state.status = 'loading'; + }, + fetchFeedMetricsSuccess(state, action: PayloadAction) { + state.status = 'loaded'; + state.feedMetrics = action.payload; + state.error = undefined; + }, + fetchFeedMetricsFailure(state, action: PayloadAction) { + state.status = 'failed'; + state.error = action.payload; + state.featuresMetrics = []; + }, + fetchAvailableFilesStart(state) { + state.availableFiles = []; + state.selectedFile = undefined; + state.status = 'loading'; + }, + fetchAvailableFilesSuccess(state, action: PayloadAction) { + state.availableFiles = action.payload; + state.status = 'loaded'; + }, + selectFile(state, action: PayloadAction) { + state.selectedFile = action.payload; + }, + }, +}); + +export const { + fetchDataStart, + fetchFeedMetricsSuccess, + fetchFeedMetricsFailure, + fetchAvailableFilesSuccess, + fetchAvailableFilesStart, + selectFile, +} = GTFSAnalyticsSlice.actions; + +export default GTFSAnalyticsSlice.reducer; diff --git a/web-app/src/app/store/gtfs-analytics-selector.ts b/web-app/src/app/store/gtfs-analytics-selector.ts new file mode 100644 index 000000000..a6cd6dadb --- /dev/null +++ b/web-app/src/app/store/gtfs-analytics-selector.ts @@ -0,0 +1,10 @@ +import { type RootState } from './store'; +import { type GTFSFeedMetrics } from '../screens/Analytics/types'; + +export const selectGTFSFeedMetrics = (state: RootState): GTFSFeedMetrics[] => + state.gtfsAnalytics.feedMetrics; +export const selectGTFSAnalyticsStatus = (state: RootState): string => + state.gtfsAnalytics.status; +export const selectGTFSAnalyticsError = ( + state: RootState, +): string | undefined => state.gtfsAnalytics.error; diff --git a/web-app/src/app/store/profile-selectors.ts b/web-app/src/app/store/profile-selectors.ts index c934353e1..84597f739 100644 --- a/web-app/src/app/store/profile-selectors.ts +++ b/web-app/src/app/store/profile-selectors.ts @@ -69,3 +69,6 @@ export const selectRegistrationError = ( state: RootState, ): ProfileError | null => selectErrorBySource(state, ProfileErrorSource.Registration); + +export const selectUserEmail = (state: RootState): string | undefined => + state.userProfile.user?.email; diff --git a/web-app/src/app/store/reducers.ts b/web-app/src/app/store/reducers.ts index 9c5266f25..adaa896ac 100644 --- a/web-app/src/app/store/reducers.ts +++ b/web-app/src/app/store/reducers.ts @@ -3,12 +3,14 @@ import profileReducer from './profile-reducer'; import feedReducer from './feed-reducer'; import datasetReducer from './dataset-reducer'; import feedsReducer from './feeds-reducer'; +import GTFSAnalyticsReducer from './analytics-reducer'; const rootReducer = combineReducers({ userProfile: profileReducer, feedProfile: feedReducer, dataset: datasetReducer, feeds: feedsReducer, + gtfsAnalytics: GTFSAnalyticsReducer, }); export default rootReducer; diff --git a/web-app/src/app/store/saga/gtfs-analytics-saga.ts b/web-app/src/app/store/saga/gtfs-analytics-saga.ts new file mode 100644 index 000000000..ef7f25f82 --- /dev/null +++ b/web-app/src/app/store/saga/gtfs-analytics-saga.ts @@ -0,0 +1,103 @@ +import { call, put, takeLatest } from 'redux-saga/effects'; +import { + fetchDataStart, + fetchFeedMetricsSuccess, + fetchFeedMetricsFailure, + fetchAvailableFilesSuccess, + selectFile, + fetchAvailableFilesStart, +} from '../analytics-reducer'; +import { + type AnalyticsFile, + type GTFSFeedMetrics, + type Metrics, +} from '../../screens/Analytics/types'; +import { getLocationName } from '../../services/feeds/utils'; +import { getAnalyticsBucketEndpoint } from '../../screens/Analytics/GTFSFeedAnalytics'; + +function* fetchFeedMetricsSaga( + action: ReturnType, +): Generator { + try { + const selectedFile = action.payload; + + // Fetch feed metrics + const feedMetricsResponse: Response = yield call( + fetch, + `${getAnalyticsBucketEndpoint()}/${selectedFile}`, + ); + if (!feedMetricsResponse.ok) { + throw new Error( + `Error ${feedMetricsResponse.status}: ${feedMetricsResponse.statusText}`, + ); + } + const feedMetrics: GTFSFeedMetrics[] = yield feedMetricsResponse.json(); + + // Add a locations_string property to each feed + feedMetrics.forEach((feed) => { + feed.locations_string = getLocationName(feed.locations); + }); + + // Fetch analytics metrics + const analyticsMetricsResponse: Response = yield call( + fetch, + `${getAnalyticsBucketEndpoint()}/feed_metrics.json`, + ); + if (!analyticsMetricsResponse.ok) { + throw new Error( + `Error ${analyticsMetricsResponse.status}: ${analyticsMetricsResponse.statusText}`, + ); + } + const analyticsMetrics: Metrics[] = yield analyticsMetricsResponse.json(); + + // Merge metrics based on feed_id + const mergedMetrics: GTFSFeedMetrics[] = feedMetrics.map( + (feed): GTFSFeedMetrics => { + const analyticsMetric = analyticsMetrics.find( + (metric) => metric.feed_id === feed.feed_id, + ); + return { + ...feed, + metrics: analyticsMetric, + }; + }, + ); + // Dispatch the merged metrics + yield put(fetchFeedMetricsSuccess(mergedMetrics)); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'An unknown error occurred'; + yield put(fetchFeedMetricsFailure(errorMessage)); + } +} + +function* fetchAvailableFilesSaga(): Generator { + try { + const response: Response = yield call( + fetch, + `${getAnalyticsBucketEndpoint()}/analytics_files.json`, + ); + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + const files: AnalyticsFile[] = yield response.json(); + yield put(fetchAvailableFilesSuccess(files)); + if (files.length > 0) { + // Select the latest file by default + const latestFile = files[files.length - 1].file_name; + yield put(selectFile(latestFile)); + } + } catch (error) { + yield put( + fetchFeedMetricsFailure( + error instanceof Error ? error.message : 'An unknown error occurred', + ), + ); + } +} + +export function* watchGTFSFetchFeedMetrics(): Generator { + yield takeLatest(fetchDataStart.type, fetchFeedMetricsSaga); + yield takeLatest(selectFile.type, fetchFeedMetricsSaga); + yield takeLatest(fetchAvailableFilesStart.type, fetchAvailableFilesSaga); +} diff --git a/web-app/src/app/store/saga/root-saga.ts b/web-app/src/app/store/saga/root-saga.ts index 8accd1bd4..4ca8a3966 100644 --- a/web-app/src/app/store/saga/root-saga.ts +++ b/web-app/src/app/store/saga/root-saga.ts @@ -4,6 +4,7 @@ import { watchProfile } from './profile-saga'; import { watchFeed } from './feed-saga'; import { watchDataset } from './dataset-saga'; import { watchFeeds } from './feeds-saga'; +import { watchGTFSFetchFeedMetrics } from './gtfs-analytics-saga'; const rootSaga = function* (): Generator { yield all([ @@ -12,6 +13,7 @@ const rootSaga = function* (): Generator { watchFeed(), watchDataset(), watchFeeds(), + watchGTFSFetchFeedMetrics(), ]); }; diff --git a/web-app/src/app/utils/analytics.ts b/web-app/src/app/utils/analytics.ts new file mode 100644 index 000000000..c5bd64cb2 --- /dev/null +++ b/web-app/src/app/utils/analytics.ts @@ -0,0 +1,62 @@ +export const featureGroups: Record = { + 'Fares v2': [ + 'Fare Products', + 'Route-Based Fares', + 'Fare Media', + 'Zone-Based Fares', + 'Time-Based Fares', + 'Transfer Fares', + ], + Pathways: ['Pathways', 'Pathways Directions', 'Levels'], + 'Flexible Services': ['Continuous Stops', 'Flex'], +}; + +/** + * Groups the features based on the feature groups + * @param features List of features + * @returns Object with grouped features and other features + */ +export function groupFeatures(features: string[]): { + groupedFeatures: Record; + otherFeatures: string[]; +} { + const groupedFeatures: Record = {}; + const otherFeatures: string[] = []; + + features?.forEach((feature) => { + let found = false; + for (const [group, groupFeatures] of Object.entries(featureGroups)) { + if (groupFeatures.includes(feature)) { + if (groupedFeatures[group] === undefined) { + groupedFeatures[group] = []; + } + groupedFeatures[group].push(feature); + found = true; + break; + } + } + if (!found) { + otherFeatures.push(feature); + } + }); + + return { groupedFeatures, otherFeatures }; +} + +/** + * Returns the color for the feature group + * @param group Feature group + * @returns Color for the feature group + */ +export function getGroupColor(group: string): string { + if (group === 'Fares v2') { + return '#d1e2ff'; + } + if (group === 'Pathways') { + return '#fdd4e0'; + } + if (group === 'Flexible Services') { + return '#fcb68e'; + } + return '#f7f7f7'; +} diff --git a/web-app/yarn.lock b/web-app/yarn.lock index c9377784d..a620b1e94 100644 --- a/web-app/yarn.lock +++ b/web-app/yarn.lock @@ -1134,6 +1134,14 @@ resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== +"@babel/runtime-corejs3@^7.12.1": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.24.8.tgz#c0ae5a1c380f8442920866d0cc51de8024507e28" + integrity sha512-DXG/BhegtMHhnN7YPIvxWd303/9aXvYFD1TjNL3CD6tUrhI2LVsg3Lck0aql5TRH29n4sj3emcROypkZVUfSuA== + dependencies: + core-js-pure "^3.30.2" + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.23.4" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz" @@ -1155,6 +1163,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.8", "@babel/runtime@^7.7.2": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" + integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz" @@ -1356,7 +1371,7 @@ enabled "2.0.x" kuler "^2.0.0" -"@emotion/babel-plugin@^11.10.6", "@emotion/babel-plugin@^11.11.0": +"@emotion/babel-plugin@^11.10.6": version "11.11.0" resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz" integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== @@ -1373,6 +1388,34 @@ source-map "^0.5.7" stylis "4.2.0" +"@emotion/babel-plugin@^11.12.0": + version "11.12.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz#7b43debb250c313101b3f885eba634f1d723fcc2" + integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.2.0" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@*", "@emotion/cache@^11.13.0", "@emotion/cache@^11.7.1": + version "11.13.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.0.tgz#8f51748b8116691dee0408b08ad758b8d246b097" + integrity sha512-hPV345J/tH0Cwk2wnU/3PBzORQ9HeX+kQSbwI+jslzpRCHE6fSGTohswksA/Ensr8znPzwfzKZCmAM9Lmlhp7g== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" + "@emotion/cache@^11.10.5", "@emotion/cache@^11.11.0": version "11.11.0" resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz" @@ -1400,32 +1443,53 @@ resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz" integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== -"@emotion/is-prop-valid@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz" - integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== + +"@emotion/is-prop-valid@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz#bd84ba972195e8a2d42462387581560ef780e4e2" + integrity sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ== dependencies: - "@emotion/memoize" "^0.8.1" + "@emotion/memoize" "^0.9.0" "@emotion/memoize@^0.8.1": version "0.8.1" resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz" integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== -"@emotion/react@^11.11.1": - version "11.11.1" - resolved "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz" - integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/react@^11.10.5", "@emotion/react@^11.13.0": + version "11.13.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.0.tgz#a9ebf827b98220255e5760dac89fa2d38ca7b43d" + integrity sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/cache" "^11.11.0" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" + "@emotion/babel-plugin" "^11.12.0" + "@emotion/cache" "^11.13.0" + "@emotion/serialize" "^1.3.0" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" hoist-non-react-statics "^3.3.1" +"@emotion/serialize@*", "@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.0.tgz#e07cadfc967a4e7816e0c3ffaff4c6ce05cb598d" + integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA== + dependencies: + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.9.0" + "@emotion/utils" "^1.4.0" + csstype "^3.0.2" + "@emotion/serialize@^1.1.1", "@emotion/serialize@^1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz" @@ -1442,27 +1506,42 @@ resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz" integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== -"@emotion/styled@^11.11.0": - version "11.11.0" - resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz" - integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== + +"@emotion/styled@^11.10.5", "@emotion/styled@^11.13.0": + version "11.13.0" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.13.0.tgz#633fd700db701472c7a5dbef54d6f9834e9fb190" + integrity sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/is-prop-valid" "^1.2.1" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" + "@emotion/babel-plugin" "^11.12.0" + "@emotion/is-prop-valid" "^1.3.0" + "@emotion/serialize" "^1.3.0" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" "@emotion/unitless@^0.8.1": version "0.8.1" resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz" integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== +"@emotion/unitless@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.9.0.tgz#8e5548f072bd67b8271877e51c0f95c76a66cbe2" + integrity sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf" + integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== + +"@emotion/utils@*", "@emotion/utils@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd" + integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ== "@emotion/utils@^1.2.0", "@emotion/utils@^1.2.1": version "1.2.1" @@ -1474,6 +1553,11 @@ resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz" integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + "@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" @@ -2421,19 +2505,6 @@ resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== -"@mui/base@5.0.0-beta.40": - version "5.0.0-beta.40" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" - integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== - dependencies: - "@babel/runtime" "^7.23.9" - "@floating-ui/react-dom" "^2.0.8" - "@mui/types" "^7.2.14" - "@mui/utils" "^5.15.14" - "@popperjs/core" "^2.11.8" - clsx "^2.1.0" - prop-types "^15.8.1" - "@mui/base@^5.0.0-beta.20": version "5.0.0-beta.30" resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.30.tgz" @@ -2447,65 +2518,78 @@ clsx "^2.0.0" prop-types "^15.8.1" -"@mui/core-downloads-tracker@^5.15.15": - version "5.15.15" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz#2bc2bda50db66c12f10aefec907c48c8f669ef59" - integrity sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg== +"@mui/base@^5.0.0-beta.40": + version "5.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" + integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + "@popperjs/core" "^2.11.8" + clsx "^2.1.0" + prop-types "^15.8.1" + +"@mui/core-downloads-tracker@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.4.tgz#a34de72acd7e81fdbcc7eeb07786205e90dda148" + integrity sha512-rNdHXhclwjEZnK+//3SR43YRx0VtjdHnUFhMSGYmAMJve+KiwEja/41EYh8V3pZKqF2geKyfcFUenTfDTYUR4w== -"@mui/icons-material@^5.14.9": - version "5.14.18" - resolved "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.18.tgz" - integrity sha512-o2z49R1G4SdBaxZjbMmkn+2OdT1bKymLvAYaB6pH59obM1CYv/0vAVm6zO31IqhwtYwXv6A7sLIwCGYTaVkcdg== +"@mui/icons-material@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.16.4.tgz#8c7e228c1e178992d89fab47e057222c8209bd7b" + integrity sha512-j9/CWctv6TH6Dou2uR2EH7UOgu79CW/YcozxCYVLJ7l03pCsiOlJ5sBArnWJxJ+nGkFwyL/1d1k8JEPMDR125A== dependencies: - "@babel/runtime" "^7.23.2" + "@babel/runtime" "^7.23.9" -"@mui/material@^5.14.9": - version "5.15.15" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.15.tgz#e3ba35f50b510aa677cec3261abddc2db7b20b59" - integrity sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA== +"@mui/material@^5.11.4", "@mui/material@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.4.tgz#992d630637d9d38620e4937fb11d0a97965fdabf" + integrity sha512-dBnh3/zRYgEVIS3OE4oTbujse3gifA0qLMmuUk13ywsDCbngJsdgwW5LuYeiT5pfA8PGPGSqM7mxNytYXgiMCw== dependencies: "@babel/runtime" "^7.23.9" - "@mui/base" "5.0.0-beta.40" - "@mui/core-downloads-tracker" "^5.15.15" - "@mui/system" "^5.15.15" - "@mui/types" "^7.2.14" - "@mui/utils" "^5.15.14" + "@mui/core-downloads-tracker" "^5.16.4" + "@mui/system" "^5.16.4" + "@mui/types" "^7.2.15" + "@mui/utils" "^5.16.4" + "@popperjs/core" "^2.11.8" "@types/react-transition-group" "^4.4.10" clsx "^2.1.0" csstype "^3.1.3" prop-types "^15.8.1" - react-is "^18.2.0" + react-is "^18.3.1" react-transition-group "^4.4.5" -"@mui/private-theming@^5.15.14": - version "5.15.14" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee" - integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw== +"@mui/private-theming@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.16.4.tgz#0118f137975b35dc4774c6d593b8fcf86594c3fc" + integrity sha512-ZsAm8cq31SJ37SVWLRlu02v9SRthxnfQofaiv14L5Bht51B0dz6yQEoVU/V8UduZDCCIrWkBHuReVfKhE/UuXA== dependencies: "@babel/runtime" "^7.23.9" - "@mui/utils" "^5.15.14" + "@mui/utils" "^5.16.4" prop-types "^15.8.1" -"@mui/styled-engine@^5.15.14": - version "5.15.14" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" - integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw== +"@mui/styled-engine@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.4.tgz#a7a8c9079c307bab91ccd65ed5dd1496ddf2a3ab" + integrity sha512-0+mnkf+UiAmTVB8PZFqOhqf729Yh0Cxq29/5cA3VAyDVTRIUUQ8FXQhiAhUIbijFmM72rY80ahFPXIm4WDbzcA== dependencies: "@babel/runtime" "^7.23.9" "@emotion/cache" "^11.11.0" csstype "^3.1.3" prop-types "^15.8.1" -"@mui/system@^5.15.15": - version "5.15.15" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.15.tgz#658771b200ce3c4a0f28e58169f02e5e718d1c53" - integrity sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ== +"@mui/system@^5.16.2", "@mui/system@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.4.tgz#c03f971ed273f0ad06c69c949c05e866ad211407" + integrity sha512-ET1Ujl2/8hbsD611/mqUuNArMCGv/fIWO/f8B3ZqF5iyPHM2aS74vhTNyjytncc4i6dYwGxNk+tLa7GwjNS0/w== dependencies: "@babel/runtime" "^7.23.9" - "@mui/private-theming" "^5.15.14" - "@mui/styled-engine" "^5.15.14" - "@mui/types" "^7.2.14" - "@mui/utils" "^5.15.14" + "@mui/private-theming" "^5.16.4" + "@mui/styled-engine" "^5.16.4" + "@mui/types" "^7.2.15" + "@mui/utils" "^5.16.4" clsx "^2.1.0" csstype "^3.1.3" prop-types "^15.8.1" @@ -2520,6 +2604,11 @@ resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== +"@mui/types@^7.2.15": + version "7.2.15" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.15.tgz#dadd232fe9a70be0d526630675dff3b110f30b53" + integrity sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q== + "@mui/utils@^5.14.14", "@mui/utils@^5.15.3": version "5.15.3" resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.15.3.tgz" @@ -2540,6 +2629,31 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/utils@^5.16.2", "@mui/utils@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.4.tgz#8e50e27a630e3d8eeb3e9d3bc31cbb0e4956f5fd" + integrity sha512-nlppYwq10TBIFqp7qxY0SvbACOXeOjeVL3pOcDsK0FT8XjrEXh9/+lkg8AEIzD16z7YfiJDQjaJG2OLkE7BxNg== + dependencies: + "@babel/runtime" "^7.23.9" + "@types/prop-types" "^15.7.12" + clsx "^2.1.1" + prop-types "^15.8.1" + react-is "^18.3.1" + +"@mui/x-date-pickers@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-7.11.0.tgz#ac98d9057b733ddec117bc0938f364316eb12b5a" + integrity sha512-+zPWs1dwe7J1nZ2iFhTgCae31BLMYMQ2VtQfHxx21Dh6gbBRy/U7YJZg1LdhfQyE093S3e4A5uMZ6PUWdne7iA== + dependencies: + "@babel/runtime" "^7.24.8" + "@mui/base" "^5.0.0-beta.40" + "@mui/system" "^5.16.2" + "@mui/utils" "^5.16.2" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + "@mui/x-tree-view@^6.17.0": version "6.17.0" resolved "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.17.0.tgz" @@ -2720,6 +2834,21 @@ resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@react-dnd/asap@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.1.tgz#5291850a6b58ce6f2da25352a64f1b0674871aab" + integrity sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg== + +"@react-dnd/invariant@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-2.0.0.tgz#09d2e81cd39e0e767d7da62df9325860f24e517e" + integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw== + +"@react-dnd/shallowequal@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a" + integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg== + "@react-firebase/auth@^0.2.10": version "0.2.10" resolved "https://registry.npmjs.org/@react-firebase/auth/-/auth-0.2.10.tgz" @@ -2993,6 +3122,37 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@tanstack/match-sorter-utils@8.15.1": + version "8.15.1" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz#715e028ff43cf79ece10bd5a757047a1016c3bba" + integrity sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw== + dependencies: + remove-accents "0.5.0" + +"@tanstack/react-table@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.16.0.tgz#92151210ff99d6925353d7a2205735d9c31af48c" + integrity sha512-rKRjnt8ostqN2fercRVOIH/dq7MAmOENCMvVlKx6P9Iokhh6woBGnIZEkqsY/vEJf1jN3TqLOb34xQGLVRuhAg== + dependencies: + "@tanstack/table-core" "8.16.0" + +"@tanstack/react-virtual@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.3.0.tgz#5a282efc1ed8da3d4e9e0f9b0c512f735d6c4b5f" + integrity sha512-QFxmTSZBniq15S0vSZ55P4ToXquMXwJypPXyX/ux7sYo6a2FX3/zWoRLLc4eIOGWTjvzqcIVNKhcuFb+OZL3aQ== + dependencies: + "@tanstack/virtual-core" "3.3.0" + +"@tanstack/table-core@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.16.0.tgz#7b58018dd3cec8e0015fe22d6bb24d18d33c891f" + integrity sha512-dCG8vQGk4js5v88/k83tTedWOwjGnIyONrKpHpfmSJB8jwFHl8GSu1sBBxbtACVAPtAQgwNxl0rw1d3RqRM1Tg== + +"@tanstack/virtual-core@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.3.0.tgz#1bf72d51f269c5a0e3ac872c6b57116767f42c25" + integrity sha512-A0004OAa1FcUkPHeeGoKgBrAgjH+uHdDPrw1L7RpkwnODYqRvoilqsHPs8cyTjMg1byZBbiNpQAq2TlFLIaQag== + "@testing-library/dom@^8.5.0": version "8.20.1" resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz" @@ -3156,6 +3316,57 @@ dependencies: cypress "*" +"@types/d3-array@^3.0.3": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-ease@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-interpolate@^3.0.1": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" + integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== + +"@types/d3-scale@^4.0.2": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" + integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-shape@^3.1.0": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" + integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time@*", "@types/d3-time@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" + integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== + +"@types/d3-timer@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + "@types/duplexify@^3.6.0": version "3.6.4" resolved "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.4.tgz" @@ -3364,6 +3575,17 @@ resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== +"@types/mui-datatables@^4.3.12": + version "4.3.12" + resolved "https://registry.yarnpkg.com/@types/mui-datatables/-/mui-datatables-4.3.12.tgz#d4bea2330206d760609933d7f4f6eefd63292db5" + integrity sha512-Xz7My6kOi7Q3LK0lNEKVF/XU0jMawIRMpROaXQxn2E8Ccmiguh19MHi/v7I8Qae8AAj/fuDx9EAHGBmvluRf3A== + dependencies: + "@emotion/react" "^11.10.5" + "@emotion/styled" "^11.10.5" + "@mui/material" "^5.11.4" + "@types/react" "*" + csstype "3.1.1 || 3.1.2" + "@types/node-forge@^1.3.0": version "1.3.10" resolved "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz" @@ -3400,6 +3622,11 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== +"@types/prop-types@^15.7.12": + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== + "@types/q@^1.5.1": version "1.5.8" resolved "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz" @@ -4456,6 +4683,15 @@ axios@^1.6.1: form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz" @@ -5157,12 +5393,17 @@ clone@^1.0.2: resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +clsx@^1.0.4, clsx@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + clsx@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz" integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== -clsx@^2.1.0: +clsx@^2.1.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -5420,6 +5661,11 @@ core-js-pure@^3.23.3: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.33.3.tgz" integrity sha512-taJ00IDOP+XYQEA2dAe4ESkmHt1fL8wzYDo3mRWQey8uO9UojlBFMneA65kMyxfYP7106c6LzWaq7/haDT6BCQ== +core-js-pure@^3.30.2: + version "3.37.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.37.1.tgz#2b4b34281f54db06c9a9a5bd60105046900553bd" + integrity sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA== + core-js@^3.19.2: version "3.33.3" resolved "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz" @@ -5712,7 +5958,7 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^3.0.2: +"csstype@3.1.1 || 3.1.2", csstype@^3.0.2: version "3.1.2" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== @@ -5776,6 +6022,77 @@ cypress@*, cypress@^13.2.0: untildify "^4.0.0" yauzl "^2.10.0" +"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-ease@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-format@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +d3-shape@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +d3-timer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" @@ -5847,6 +6164,11 @@ debug@^3.1.0, debug@^3.2.7: dependencies: ms "^2.1.1" +decimal.js-light@^2.4.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decimal.js@^10.2.1: version "10.4.3" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" @@ -6045,6 +6367,15 @@ dlv@^1.1.3: resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== +dnd-core@^11.1.3: + version "11.1.3" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-11.1.3.tgz#f92099ba7245e49729d2433157031a6267afcc98" + integrity sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA== + dependencies: + "@react-dnd/asap" "^4.0.0" + "@react-dnd/invariant" "^2.0.0" + redux "^4.0.4" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz" @@ -6083,7 +6414,7 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" -dom-helpers@^5.0.1: +dom-helpers@^5.0.1, dom-helpers@^5.1.3: version "5.2.1" resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== @@ -6853,7 +7184,7 @@ eventemitter2@6.4.7: resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz" integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== -eventemitter3@^4.0.0: +eventemitter3@^4.0.0, eventemitter3@^4.0.1: version "4.0.7" resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -7063,6 +7394,11 @@ fast-diff@^1.1.2: resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== +fast-equals@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d" + integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== + fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" @@ -7355,7 +7691,7 @@ fn.name@1.x.x: resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.0.0, follow-redirects@^1.15.0: +follow-redirects@^1.0.0, follow-redirects@^1.15.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -7984,7 +8320,12 @@ heap-js@^2.2.0: resolved "https://registry.npmjs.org/heap-js/-/heap-js-2.3.0.tgz" integrity sha512-E5303mzwQ+4j/n2J0rDvEPBN7GKjhis10oHiYOgjxsmxYgqG++hz9NyLLOXttzH8as/DyiBHYpUrJTZWYaMo8Q== -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: +highlight-words@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/highlight-words/-/highlight-words-1.2.2.tgz#9875b75d11814d7356b24f23feeb7d77761fa867" + integrity sha512-Mf4xfPXYm8Ay1wTibCrHpNWeR2nUMynMVFkXCi4mbl+TEgmNOe+I4hV7W3OCZcSvzGL6kupaqpfHOemliMTGxQ== + +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -8352,6 +8693,11 @@ internal-slot@^1.0.4, internal-slot@^1.0.5: hasown "^2.0.0" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + ip-regex@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz" @@ -9899,11 +10245,21 @@ lodash._objecttypes@~2.4.1: resolved "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz" integrity sha512-XpqGh1e7hhkOzftBfWE7zt+Yn9mVHFkDhicVttvKLsoCMLVVL+xTQjfjB4X4vtznauxv0QZ5ZAeqjvat0dh62Q== +lodash.assignwith@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz#127a97f02adc41751a954d24b0de17e100e038eb" + integrity sha512-ZznplvbvtjK2gMvnQ1BR/zqPFZmS6jbK4p+6Up4xcRYA7yMIwxHCfbTcrYxXKzzqLsQ05eJPVznEW3tuwV7k1g== + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" @@ -9919,6 +10275,11 @@ lodash.difference@^4.5.0: resolved "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz" integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== +lodash.find@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" + integrity sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg== + lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz" @@ -9939,6 +10300,11 @@ lodash.isboolean@^3.0.3: resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz" integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz" @@ -9966,6 +10332,11 @@ lodash.isstring@^4.0.1: resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" + integrity sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" @@ -9991,6 +10362,11 @@ lodash.sortby@^4.7.0: resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz" integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== +lodash.throttle@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== + lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz" @@ -10162,6 +10538,16 @@ marked@^4.0.10, marked@^4.0.14: resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== +material-react-table@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/material-react-table/-/material-react-table-2.13.0.tgz#445df17dd266a3177c2a1bb3114bd852f752e3da" + integrity sha512-ds4/cupDsXvoz8K8OpM3UqUyqKoAMkVdvmvP/+ovuWA23fPcjYvFFkUpBxtnZq5GKWM0+SZWzr14KQ1DgKCaFQ== + dependencies: + "@tanstack/match-sorter-utils" "8.15.1" + "@tanstack/react-table" "8.16.0" + "@tanstack/react-virtual" "3.3.0" + highlight-words "1.2.2" + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz" @@ -10401,6 +10787,35 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mui-datatables@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/mui-datatables/-/mui-datatables-4.3.0.tgz#445d6da0960005e242d4f69693702a33daba05ae" + integrity sha512-LFliQwNnnxW03IO+V3q/ORxZsOHkzl53iGogLbjUJzme47hNEN106dM0ie8oMSc0heYJY0J07oZmKm7Xn3X7IQ== + dependencies: + "@babel/runtime-corejs3" "^7.12.1" + "@emotion/cache" "^11.7.1" + clsx "^1.1.1" + lodash.assignwith "^4.2.0" + lodash.clonedeep "^4.5.0" + lodash.debounce "^4.0.8" + lodash.find "^4.6.0" + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + lodash.isundefined "^3.0.1" + lodash.memoize "^4.1.2" + lodash.merge "^4.6.2" + prop-types "^15.7.2" + react-dnd "^11.1.3" + react-dnd-html5-backend "^11.1.3" + react-sortable-tree-patch-react-17 "^2.9.0" + react-to-print "^2.8.0" + tss-react "^3.6.0" + +mui-nested-menu@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/mui-nested-menu/-/mui-nested-menu-3.4.0.tgz#e104b1b492e0a9ef95820e97827851310c3a56ba" + integrity sha512-QUZqsCKW4tX1GwcbemBvf++ZvKsRXvHdZ+CT6GvnHvusucmEB18WKuJjAZVD8L5trXS98+9psie11Ev3FTez2A== + multicast-dns@^7.2.5: version "7.2.5" resolved "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz" @@ -11742,7 +12157,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.5.0, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.5.0, prop-types@^15.5.9, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -11923,7 +12338,7 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -raf@^3.4.1: +raf@^3.2.0, raf@^3.4.1: version "3.4.1" resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== @@ -12021,6 +12436,48 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" +react-display-name@^0.2.0: + version "0.2.5" + resolved "https://registry.yarnpkg.com/react-display-name/-/react-display-name-0.2.5.tgz#304c7cbfb59ee40389d436e1a822c17fe27936c6" + integrity sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg== + +react-dnd-html5-backend@^11.1.3: + version "11.1.3" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz#2749f04f416ec230ea193f5c1fbea2de7dffb8f7" + integrity sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw== + dependencies: + dnd-core "^11.1.3" + +react-dnd-scrollzone-patch-react-17@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/react-dnd-scrollzone-patch-react-17/-/react-dnd-scrollzone-patch-react-17-1.0.2.tgz#aa7e4c54268fe109246e790f1412427481b63067" + integrity sha512-Wfhyc/Y/Veim29REBYm8nMmtDB5IwSmPPhXIuabBgsEa1MrVsuOwK9+7LmuP+mGbDOEP/S6G8+5XvDqPlRFK2g== + dependencies: + hoist-non-react-statics "^3.1.0" + lodash.throttle "^4.0.1" + prop-types "^15.5.9" + raf "^3.2.0" + react-display-name "^0.2.0" + +react-dnd@^11.1.3: + version "11.1.3" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-11.1.3.tgz#f9844f5699ccc55dfc81462c2c19f726e670c1af" + integrity sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ== + dependencies: + "@react-dnd/shallowequal" "^2.0.0" + "@types/hoist-non-react-statics" "^3.3.1" + dnd-core "^11.1.3" + hoist-non-react-statics "^3.3.0" + +react-dom@^17.0.0: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + "react-dom@^17.0.0 || ^18.0.0": version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" @@ -12065,7 +12522,7 @@ react-i18next@^14.1.2: "@babel/runtime" "^7.23.9" html-parse-stringify "^3.0.1" -react-is@^16.13.1, react-is@^16.7.0: +react-is@^16.10.2, react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -12080,6 +12537,11 @@ react-is@^18.0.0, react-is@^18.2.0: resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + react-leaflet@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-4.2.1.tgz#c300e9eccaf15cb40757552e181200aa10b94780" @@ -12087,6 +12549,11 @@ react-leaflet@^4.2.1: dependencies: "@react-leaflet/core" "^2.1.0" +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-loading-overlay-ts@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/react-loading-overlay-ts/-/react-loading-overlay-ts-2.0.2.tgz" @@ -12183,6 +12650,35 @@ react-scripts@5.0.1: optionalDependencies: fsevents "^2.3.2" +react-smooth@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a" + integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w== + dependencies: + fast-equals "^5.0.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + +react-sortable-tree-patch-react-17@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-sortable-tree-patch-react-17/-/react-sortable-tree-patch-react-17-2.9.0.tgz#d80ef61a5b941a8cc52570f6aa2c3059076d9f21" + integrity sha512-Ngtdbf78OfjqCxLj7+N+K4zM9d1mQ/tfnUsOfICFDzNa5JHg6AjixAj69ijvz0ykEiA9lYop+0Fm4KCOqCdlKA== + dependencies: + lodash.isequal "^4.5.0" + prop-types "^15.6.1" + react "^17.0.0" + react-dnd "^11.1.3" + react-dnd-html5-backend "^11.1.3" + react-dnd-scrollzone-patch-react-17 "^1.0.2" + react-dom "^17.0.0" + react-lifecycles-compat "^3.0.4" + react-virtualized "^9.21.2" + +react-to-print@^2.8.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/react-to-print/-/react-to-print-2.15.1.tgz#c9a6732cadf1118fc90d886b54a9388c419561b9" + integrity sha512-1foogIFbCpzAVxydkhBiDfMiFYhIMphiagDOfcG4X/EcQ+fBPqJ0rby9Wv/emzY1YLkIQy/rEgOrWQT+rBKhjw== + react-transition-group@4.4.5, react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" @@ -12193,6 +12689,26 @@ react-transition-group@4.4.5, react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" +react-virtualized@^9.21.2: + version "9.22.5" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.5.tgz#bfb96fed519de378b50d8c0064b92994b3b91620" + integrity sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ== + dependencies: + "@babel/runtime" "^7.7.2" + clsx "^1.0.4" + dom-helpers "^5.1.3" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.4" + +react@^17.0.0: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + "react@^17.0.0 || ^18.0.0": version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" @@ -12243,6 +12759,27 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +recharts-scale@^0.4.4: + version "0.4.5" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" + integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== + dependencies: + decimal.js-light "^2.4.1" + +recharts@^2.12.7: + version "2.12.7" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.7.tgz#c7f42f473a257ff88b43d88a92530930b5f9e773" + integrity sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ== + dependencies: + clsx "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.21" + react-is "^16.10.2" + react-smooth "^4.0.0" + recharts-scale "^0.4.4" + tiny-invariant "^1.3.1" + victory-vendor "^36.6.8" + recursive-readdir@^2.2.2: version "2.2.3" resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz" @@ -12382,6 +12919,11 @@ relateurl@^0.2.7: resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== +remove-accents@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" + integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== + render-and-add-props@^0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/render-and-add-props/-/render-and-add-props-0.5.0.tgz" @@ -12677,6 +13219,14 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz" @@ -13150,16 +13700,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13242,14 +13783,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13622,6 +14156,11 @@ tiny-case@^1.0.3: resolved "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz" integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== +tiny-invariant@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tiny-warning@^1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" @@ -13768,6 +14307,15 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6 resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tss-react@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-3.7.1.tgz#119647731490f9e7e62c7f6a38a78df981929a4b" + integrity sha512-dfWUoxBlKZfIG9UC1A2h02OmcE/Ni0itCmmZu94E9g+KyBhKMHKcsKvUm0bNlRqTmYjXiCgPJDmj5fyc8CSrLg== + dependencies: + "@emotion/cache" "*" + "@emotion/serialize" "*" + "@emotion/utils" "*" + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" @@ -14161,6 +14709,26 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +victory-vendor@^36.6.8: + version "36.9.2" + resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801" + integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ== + dependencies: + "@types/d3-array" "^3.0.3" + "@types/d3-ease" "^3.0.0" + "@types/d3-interpolate" "^3.0.1" + "@types/d3-scale" "^4.0.2" + "@types/d3-shape" "^3.1.0" + "@types/d3-time" "^3.0.0" + "@types/d3-timer" "^3.0.0" + d3-array "^3.1.6" + d3-ease "^3.0.1" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-shape "^3.1.0" + d3-time "^3.0.0" + d3-timer "^3.0.1" + void-elements@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" @@ -14681,7 +15249,7 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -14699,15 +15267,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"