diff --git a/frontend/src/component/impact-metrics/ChartItem.tsx b/frontend/src/component/impact-metrics/ChartItem.tsx index a8d7daa51a..6793670a1e 100644 --- a/frontend/src/component/impact-metrics/ChartItem.tsx +++ b/frontend/src/component/impact-metrics/ChartItem.tsx @@ -44,7 +44,6 @@ const StyledWidget = styled(Paper)(({ theme }) => ({ display: 'flex', flexDirection: 'column', height: '100%', - overflow: 'hidden', })); const StyledChartContent = styled(Box)({ diff --git a/frontend/src/component/impact-metrics/GridLayoutWrapper.tsx b/frontend/src/component/impact-metrics/GridLayoutWrapper.tsx index dd6eec4eed..d0f892b5c1 100644 --- a/frontend/src/component/impact-metrics/GridLayoutWrapper.tsx +++ b/frontend/src/component/impact-metrics/GridLayoutWrapper.tsx @@ -1,19 +1,19 @@ import type { FC, ReactNode } from 'react'; import { useMemo, useCallback } from 'react'; -import { Responsive, WidthProvider } from 'react-grid-layout'; -import { styled } from '@mui/material'; +import GridLayout, { WidthProvider } from 'react-grid-layout'; +import { styled, useTheme, useMediaQuery } from '@mui/material'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; -const ResponsiveGridLayout = WidthProvider(Responsive); +const ResponsiveGridLayout = WidthProvider(GridLayout); const StyledGridContainer = styled('div')(({ theme }) => ({ '& .react-grid-item': { borderRadius: `${theme.shape.borderRadiusMedium}px`, }, - '& .react-resizable-handle': { - '&::after': { - opacity: 0.5, + '& .grid-item-drag-handle': { + [theme.breakpoints.down('md')]: { + display: 'none', }, }, })); @@ -32,65 +32,74 @@ export type GridItem = { static?: boolean; }; +type LayoutItem = { + i: string; + x: number; + y: number; + w: number; + h: number; + minW?: number; + minH?: number; + maxW?: number; + maxH?: number; + static?: boolean; +}; + type GridLayoutWrapperProps = { items: GridItem[]; - onLayoutChange?: (layout: unknown[]) => void; + onLayoutChange?: (layout: LayoutItem[]) => void; cols?: { lg: number; md: number; sm: number; xs: number; xxs: number }; rowHeight?: number; - margin?: [number, number]; - isDraggable?: boolean; - isResizable?: boolean; - compactType?: 'vertical' | 'horizontal' | null; }; export const GridLayoutWrapper: FC = ({ items, onLayoutChange, - cols = { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }, + cols = { lg: 12, md: 12, sm: 6, xs: 4, xxs: 2 }, rowHeight = 180, - margin = [16, 16], - isDraggable = true, - isResizable = true, - compactType = 'vertical', }) => { - const layouts = useMemo(() => { - const baseLayout = items.map((item, index) => ({ + const theme = useTheme(); + const isMobileBreakpoint = useMediaQuery(theme.breakpoints.down('md')); + + const layout = useMemo(() => { + if (isMobileBreakpoint) { + let currentY = 0; + return items.map((item) => { + const layoutItem = { + i: item.id, + x: 0, + y: currentY, + w: cols.xs, + h: item.h ?? 4, + minW: cols.xs, + minH: item.minH ?? 3, + maxW: cols.xs, + maxH: item.maxH ?? 8, + static: false, + }; + currentY += layoutItem.h; + return layoutItem; + }); + } + + return items.map((item, index) => ({ i: item.id, - x: item.x ?? (index % cols.lg) * (item.w ?? 6), - y: item.y ?? Math.floor(index / cols.lg) * (item.h ?? 4), - w: item.w ?? 6, + x: + item.x ?? + (index % Math.floor(cols.lg / (item.w ?? 4))) * (item.w ?? 4), + y: + item.y ?? + Math.floor(index / Math.floor(cols.lg / (item.w ?? 4))) * + (item.h ?? 4), + w: item.w ?? 4, h: item.h ?? 4, - minW: item.minW ?? 3, + minW: item.minW ?? 4, minH: item.minH ?? 3, maxW: item.maxW ?? 12, maxH: item.maxH ?? 8, static: item.static ?? false, })); - - return { - lg: baseLayout, - md: baseLayout.map((item) => ({ - ...item, - w: Math.min(item.w, cols.md), - x: Math.min(item.x, cols.md - item.w), - })), - sm: baseLayout.map((item) => ({ - ...item, - w: Math.min(item.w, cols.sm), - x: Math.min(item.x, cols.sm - item.w), - })), - xs: baseLayout.map((item) => ({ - ...item, - w: Math.min(item.w, cols.xs), - x: Math.min(item.x, cols.xs - item.w), - })), - xxs: baseLayout.map((item) => ({ - ...item, - w: Math.min(item.w, cols.xxs), - x: Math.min(item.x, cols.xxs - item.w), - })), - }; - }, [items, cols]); + }, [items, cols, isMobileBreakpoint]); const children = useMemo( () => items.map((item) =>
{item.component}
), @@ -98,31 +107,36 @@ export const GridLayoutWrapper: FC = ({ ); const handleLayoutChange = useCallback( - (layout: unknown[], layouts: unknown) => { - onLayoutChange?.(layout); + (layout: LayoutItem[]) => { + if (!isMobileBreakpoint) { + onLayoutChange?.(layout); + } }, - [onLayoutChange], + [onLayoutChange, isMobileBreakpoint], ); return ( {children} diff --git a/frontend/src/component/impact-metrics/ImpactMetrics.tsx b/frontend/src/component/impact-metrics/ImpactMetrics.tsx index 0d54641acc..3cbbbc5027 100644 --- a/frontend/src/component/impact-metrics/ImpactMetrics.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetrics.tsx @@ -14,8 +14,8 @@ const StyledEmptyState = styled(Paper)(({ theme }) => ({ textAlign: 'center', padding: theme.spacing(8), backgroundColor: theme.palette.background.default, - borderRadius: theme.shape.borderRadius * 2, - border: `2px dashed ${theme.palette.divider}`, + borderRadius: `${theme.shape.borderRadiusMedium}px`, + boxShadow: 'none', })); export const ImpactMetrics: FC = () => { @@ -144,9 +144,6 @@ export const ImpactMetrics: FC = () => { ) : null} diff --git a/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx b/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx index a28ec3060f..fc9b7f871a 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx @@ -104,6 +104,11 @@ export const ImpactMetricsChart: FC = ({ }, tooltipFormat: 'PPpp', }, + ticks: { + maxRotation: 45, + minRotation: 45, + maxTicksLimit: 8, + }, }, y: { beginAtZero, diff --git a/frontend/src/component/impact-metrics/hooks/useChartData.ts b/frontend/src/component/impact-metrics/hooks/useChartData.ts index ea3e8356db..f7ea329d5e 100644 --- a/frontend/src/component/impact-metrics/hooks/useChartData.ts +++ b/frontend/src/component/impact-metrics/hooks/useChartData.ts @@ -43,15 +43,25 @@ export const useChartData = ( ], }; } else { + // Create a comprehensive timestamp range for consistent X-axis const allTimestamps = new Set(); timeSeriesData.forEach((series) => { series.data.forEach(([timestamp]) => { allTimestamps.add(timestamp); }); }); + + if (allTimestamps.size === 0) { + return { + labels: [], + datasets: [], + }; + } + const sortedTimestamps = Array.from(allTimestamps).sort( (a, b) => a - b, ); + const labels = sortedTimestamps.map( (timestamp) => new Date(timestamp * 1000), ); @@ -72,7 +82,6 @@ export const useChartData = ( borderColor: color, backgroundColor: color, fill: false, - spanGaps: false, }; }); diff --git a/frontend/src/component/impact-metrics/utils.ts b/frontend/src/component/impact-metrics/utils.ts index 8c4e292d39..47a580a735 100644 --- a/frontend/src/component/impact-metrics/utils.ts +++ b/frontend/src/component/impact-metrics/utils.ts @@ -7,22 +7,24 @@ export const getTimeUnit = (selectedRange: string) => { case 'week': return 'day'; case 'month': - return 'day'; + return 'week'; default: - return 'hour'; + return 'day'; } }; export const getDisplayFormat = (selectedRange: string) => { switch (selectedRange) { case 'hour': + return 'HH:mm'; case 'day': return 'HH:mm'; case 'week': + return 'MMM dd'; case 'month': return 'MMM dd'; default: - return 'MMM dd HH:mm'; + return 'MMM dd'; } }; diff --git a/frontend/src/component/insights/components/LineChart/ChartTooltip/ChartTooltip.tsx b/frontend/src/component/insights/components/LineChart/ChartTooltip/ChartTooltip.tsx index c4eac7716d..ff0b8891b2 100644 --- a/frontend/src/component/insights/components/LineChart/ChartTooltip/ChartTooltip.tsx +++ b/frontend/src/component/insights/components/LineChart/ChartTooltip/ChartTooltip.tsx @@ -1,5 +1,6 @@ import { Box, Paper, styled, Typography } from '@mui/material'; import type { TooltipItem } from 'chart.js'; +import { Truncator } from 'component/common/Truncator/Truncator'; import type React from 'react'; import type { FC, VFC } from 'react'; import { objectId } from 'utils/objectId'; @@ -32,12 +33,13 @@ const StyledItem = styled('li')(({ theme }) => ({ marginBottom: theme.spacing(0.5), display: 'flex', alignItems: 'center', + fontSize: theme.typography.body2.fontSize, })); const StyledLabelIcon = styled('span')(({ theme }) => ({ display: 'inline-block', - width: 8, - height: 8, + minWidth: 8, + minHeight: 8, borderRadius: '50%', marginRight: theme.spacing(1), })); @@ -119,14 +121,7 @@ export const ChartTooltip: VFC = ({ tooltip }) => ( > {' '} - - {item.title} - + {item.title} ))}