Skip to content

Refine Experimental Workout Analytics UI for Clarity and Professional Polish #6043

@arii

Description

@arii
Original Issue Body

To improve the visual density, accessibility, and professional polish of the HRM experimental analytics components, we need to address several "AI slop" indicators: unformatted raw data (long floats), poor spatial utilization, and missing semantic color coding for workout states and heart rate zones.

Below are the high-level technical recommendations and refactored code snippets to align with the repository's specified stack and Material-UI theme.

1. Refactor WorkoutSummary.tsx

The current implementation uses a generic layout. We should utilize the custom palette defined in theme.ts to color-code the workout status and improve typography weight for data readability.

// app/client/experimental/components/WorkoutSummary.tsx
'use client'
import { Card, CardContent, Typography, Grid, Box, Chip } from '@mui/material'
import { useTheme } from '@mui/material/styles'
import { formatDuration } from '@/lib/utils'

interface WorkoutSummaryProps {
  duration: number
  calories: number
  status: 'idle' | 'running' | 'paused' | 'finished'
}

const WorkoutSummary = ({ duration, calories, status }: WorkoutSummaryProps) => {
  const theme = useTheme();
  
  // Map status to theme colors defined in theme.ts
  const statusColor = theme.palette.custom[status === 'running' ? 'running' : 'idle'];

  return (
    <Card elevation={2}>
      <CardContent>
        <Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
          <Typography variant="h6" fontWeight="bold">Workout Summary</Typography>
          <Chip 
            label={status.toUpperCase()} 
            sx={{ backgroundColor: statusColor, color: '#fff', fontWeight: 'bold' }} 
          />
        </Box>
        <Grid container spacing={2}>
          <Grid item xs={6}>
            <Typography variant="caption" color="textSecondary">Duration</Typography>
            <Typography variant="h5" sx={{ fontFamily: 'Monospace' }}>
              {formatDuration(duration, { unit: 'seconds', format: 'HH:MM:SS' })}
            </Typography>
          </Grid>
          <Grid item xs={6}>
            <Typography variant="caption" color="textSecondary">Calories</Typography>
            <Typography variant="h5">{calories.toFixed(0)} <small>kcal</small></Typography>
          </Grid>
        </Grid>
      </CardContent>
    </Card>
  )
}

2. Refactor ZoneDistribution.tsx

The HTML output showed raw floating-point seconds (e.g., 0:27.713000...). We must truncate these for the UI and use a horizontal bar representation to communicate "Time in Zone" effectively.

// app/client/experimental/components/ZoneDistribution.tsx
'use client'
import { Card, CardContent, Typography, Box, LinearProgress } from '@mui/material'
import { HrZoneName } from '@/lib/workout-session-storage'

// ... existing props interface

const ZoneDistribution = ({ timeInZones, totalDuration }: ZoneDistributionProps) => {
  return (
    <Card elevation={2}>
      <CardContent>
        <Typography variant="h6" fontWeight="bold" gutterBottom>Time in Zones</Typography>
        {Object.entries(timeInZones)
          .filter(([zone]) => ![HrZoneName.NoData, HrZoneName.Unknown].includes(zone as HrZoneName))
          .map(([zone, time]) => {
            const percentage = totalDuration > 0 ? (time / totalDuration) * 100 : 0;
            if (percentage < 1) return null; // Hide negligible data

            return (
              <Box key={zone} mb={2}>
                <Box display="flex" justifyContent="space-between" mb={0.5}>
                  <Typography variant="body2" fontWeight="medium">{zone}</Typography>
                  <Typography variant="body2" color="textSecondary">
                    {Math.floor(time / 60)}m {Math.floor(time % 60)}s ({percentage.toFixed(1)}%)
                  </Typography>
                </Box>
                <LinearProgress 
                  variant="determinate" 
                  value={percentage} 
                  sx={{ height: 8, borderRadius: 4 }}
                />
              </Box>
            );
          })}
      </CardContent>
    </Card>
  );
}

3. Improve HeartRateTimeSeries.tsx

The chart is currently using a default purple stroke. For an HRM app, the line color should ideally reflect the current intensity or use a more energetic color.

  • Tip: Use a ResponsiveContainer with a fixed minHeight to prevent layout shift.
  • Grid: Lighten the CartesianGrid stroke for better contrast.

Key Improvements Summary:

  • Precision Control: Implemented toFixed(0) for calories and removed long float decimals in duration to reduce visual noise.
  • Semantic Color: Integrated the custom palette from theme.ts to provide immediate visual feedback on workout state.
  • Accessibility: Switched to a Grid layout with explicit caption labels, ensuring screen readers clearly associate labels with values.

To improve the visual density, accessibility, and professional polish of the HRM experimental analytics components, we need to address several "AI slop" indicators: unformatted raw data (long floats), poor spatial utilization, and missing semantic color coding for workout states and heart rate zones.

Below are the high-level technical recommendations and refactored code snippets to align with the repository's specified stack and Material-UI theme.

1. Refactor WorkoutSummary.tsx

The current implementation uses a generic layout. We should utilize the custom palette defined in theme.ts to color-code the workout status and improve typography weight for data readability.

// app/client/experimental/components/WorkoutSummary.tsx
'use client'
import { Card, CardContent, Typography, Grid, Box, Chip } from '@mui/material'
import { useTheme } from '@mui/material/styles'
import { formatDuration } from '@/lib/utils'

interface WorkoutSummaryProps {
  duration: number
  calories: number
  status: 'idle' | 'running' | 'paused' | 'finished'
}

const WorkoutSummary = ({ duration, calories, status }: WorkoutSummaryProps) => {
  const theme = useTheme();
  
  // Map status to theme colors defined in theme.ts
  const statusColor = theme.palette.custom[status === 'running' ? 'running' : 'idle'];

  return (
    <Card elevation={2}>
      <CardContent>
        <Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
          <Typography variant="h6" fontWeight="bold">Workout Summary</Typography>
          <Chip 
            label={status.toUpperCase()} 
            sx={{ backgroundColor: statusColor, color: '#fff', fontWeight: 'bold' }} 
          />
        </Box>
        <Grid container spacing={2}>
          <Grid item xs={6}>
            <Typography variant="caption" color="textSecondary">Duration</Typography>
            <Typography variant="h5" sx={{ fontFamily: 'Monospace' }}>
              {formatDuration(duration, { unit: 'seconds', format: 'HH:MM:SS' })}
            </Typography>
          </Grid>
          <Grid item xs={6}>
            <Typography variant="caption" color="textSecondary">Calories</Typography>
            <Typography variant="h5">{calories.toFixed(0)} <small>kcal</small></Typography>
          </Grid>
        </Grid>
      </CardContent>
    </Card>
  )
}

2. Refactor ZoneDistribution.tsx

The HTML output showed raw floating-point seconds (e.g., 0:27.713000...). We must truncate these for the UI and use a horizontal bar representation to communicate "Time in Zone" effectively.

// app/client/experimental/components/ZoneDistribution.tsx
'use client'
import { Card, CardContent, Typography, Box, LinearProgress } from '@mui/material'
import { HrZoneName } from '@/lib/workout-session-storage'

// ... existing props interface

const ZoneDistribution = ({ timeInZones, totalDuration }: ZoneDistributionProps) => {
  return (
    <Card elevation={2}>
      <CardContent>
        <Typography variant="h6" fontWeight="bold" gutterBottom>Time in Zones</Typography>
        {Object.entries(timeInZones)
          .filter(([zone]) => ![HrZoneName.NoData, HrZoneName.Unknown].includes(zone as HrZoneName))
          .map(([zone, time]) => {
            const percentage = totalDuration > 0 ? (time / totalDuration) * 100 : 0;
            if (percentage < 1) return null; // Hide negligible data

            return (
              <Box key={zone} mb={2}>
                <Box display="flex" justifyContent="space-between" mb={0.5}>
                  <Typography variant="body2" fontWeight="medium">{zone}</Typography>
                  <Typography variant="body2" color="textSecondary">
                    {Math.floor(time / 60)}m {Math.floor(time % 60)}s ({percentage.toFixed(1)}%)
                  </Typography>
                </Box>
                <LinearProgress 
                  variant="determinate" 
                  value={percentage} 
                  sx={{ height: 8, borderRadius: 4 }}
                />
              </Box>
            );
          })}
      </CardContent>
    </Card>
  );
}

3. Improve HeartRateTimeSeries.tsx

The chart is currently using a default purple stroke. For an HRM app, the line color should ideally reflect the current intensity or use a more energetic color.

  • Tip: Use a ResponsiveContainer with a fixed minHeight to prevent layout shift.
  • Grid: Lighten the CartesianGrid stroke for better contrast.

Key Improvements Summary:

  • Precision Control: Implemented toFixed(0) for calories and removed long float decimals in duration to reduce visual noise.
  • Semantic Color: Integrated the custom palette from theme.ts to provide immediate visual feedback on workout state.
  • Accessibility: Switched to a Grid layout with explicit caption labels, ensuring screen readers clearly associate labels with values.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions