Goals UI Guidelines

Goals UI Guidelines

This document provides UI/UX guidelines and patterns for implementing goal-related interfaces in the MTD application.

Design Principles

Visual Hierarchy

  • Goal name and progress are the primary visual elements
  • Status indicators use consistent color coding
  • Deadlines and assignees are secondary information
  • Actions are contextually placed

Progressive Disclosure

  • Show essential information in list views
  • Reveal detailed metrics on hover/click
  • Use expandable sections for advanced options
  • Keep creation wizards step-based

Component Patterns

Goal Card Component

A standard goal card should include:

<Card sx={{ p: 2, cursor: 'pointer' }}>
  <Box display="flex" justifyContent="space-between" alignItems="start">
    <Box flex={1}>
      <Typography variant="h6">{goal.name}</Typography>
      <Typography variant="body2" color="text.secondary">
        {goal.description}
      </Typography>
    </Box>
    <GoalStatusChip status={goal.status} />
  </Box>
  
  <Box mt={2}>
    <GoalProgressBar 
      value={goal.progressPercentage} 
      status={goal.status}
    />
    <Typography variant="caption" sx={{ mt: 0.5 }}>
      {goal.currentValue} / {goal.targetValue} {getUnitLabel(goal)}
    </Typography>
  </Box>
  
  <Box display="flex" justifyContent="space-between" mt={2}>
    <AvatarGroup assignees={goal.assignees} />
    <Typography variant="caption" color="text.secondary">
      {formatTimeRemaining(goal.endDate)}
    </Typography>
  </Box>
</Card>

Progress Visualization

Linear Progress Bar

<LinearProgress
  variant="determinate"
  value={progressPercentage}
  sx={{
    height: 8,
    borderRadius: 4,
    backgroundColor: 'grey.200',
    '& .MuiLinearProgress-bar': {
      borderRadius: 4,
      backgroundColor: getProgressColor(status)
    }
  }}
/>

Circular Progress (for compact views)

<Box position="relative" display="inline-flex">
  <CircularProgress
    variant="determinate"
    value={progressPercentage}
    size={60}
    thickness={4}
    sx={{ color: getProgressColor(status) }}
  />
  <Box
    position="absolute"
    top={0}
    left={0}
    bottom={0}
    right={0}
    display="flex"
    alignItems="center"
    justifyContent="center"
  >
    <Typography variant="caption" component="div">
      {`${Math.round(progressPercentage)}%`}
    </Typography>
  </Box>
</Box>

Status Indicators

Status Colors

const statusColors = {
  NOT_STARTED: '#9E9E9E',    // Grey
  ON_TRACK: '#4CAF50',       // Green
  AT_RISK: '#FF9800',        // Orange
  OFF_TRACK: '#F44336',      // Red
  ACHIEVED: '#2196F3',       // Blue
  MISSED: '#795548'          // Brown
};

Status Chip Component

<Chip
  label={getStatusLabel(status)}
  size="small"
  sx={{
    backgroundColor: alpha(statusColors[status], 0.1),
    color: statusColors[status],
    fontWeight: 'medium'
  }}
/>

Layout Patterns

Goals Dashboard

<Grid container spacing={3}>
  {/* Summary Cards */}
  <Grid item xs={12}>
    <Grid container spacing={2}>
      <Grid item xs={12} sm={6} md={3}>
        <SummaryCard title="Active Goals" value={activeCount} />
      </Grid>
      <Grid item xs={12} sm={6} md={3}>
        <SummaryCard title="On Track" value={onTrackCount} color="success" />
      </Grid>
      <Grid item xs={12} sm={6} md={3}>
        <SummaryCard title="At Risk" value={atRiskCount} color="warning" />
      </Grid>
      <Grid item xs={12} sm={6} md={3}>
        <SummaryCard title="Achieved" value={achievedCount} color="primary" />
      </Grid>
    </Grid>
  </Grid>
  
  {/* Goals List */}
  <Grid item xs={12} md={8}>
    <Paper>
      <GoalsList goals={goals} />
    </Paper>
  </Grid>
  
  {/* Recent Updates */}
  <Grid item xs={12} md={4}>
    <Paper>
      <RecentGoalUpdates />
    </Paper>
  </Grid>
</Grid>

Goal Details Page

<Container maxWidth="lg">
  <Grid container spacing={3}>
    {/* Header */}
    <Grid item xs={12}>
      <GoalHeader goal={goal} onEdit={handleEdit} />
    </Grid>
    
    {/* Main Content */}
    <Grid item xs={12} md={8}>
      <Stack spacing={3}>
        <ProgressSection goal={goal} />
        <ActivityBreakdown goal={goal} />
        <TimelineChart goal={goal} />
      </Stack>
    </Grid>
    
    {/* Sidebar */}
    <Grid item xs={12} md={4}>
      <Stack spacing={3}>
        <DetailsCard goal={goal} />
        <AssigneesCard goal={goal} />
        <StatusUpdatesCard goal={goal} />
      </Stack>
    </Grid>
  </Grid>
</Container>

Interactive Elements

Date Selection

Use the DatePickerWithPresets component:

<DatePickerWithPresets
  context="goal"
  type="start"
  value={startDate}
  onChange={setStartDate}
  presets={[
    { label: 'This Week', getValue: () => startOfWeek(new Date()) },
    { label: 'This Month', getValue: () => startOfMonth(new Date()) },
    { label: 'This Quarter', getValue: () => startOfQuarter(new Date()) },
    { label: 'This Year', getValue: () => startOfYear(new Date()) }
  ]}
/>

Entity Selection (Pillars/Activities)

<Autocomplete
  multiple
  options={activities}
  value={selectedActivities}
  onChange={(_, newValue) => setSelectedActivities(newValue)}
  groupBy={(option) => option.pillarName}
  getOptionLabel={(option) => option.name}
  renderInput={(params) => (
    <TextField
      {...params}
      label="Select Activities to Track"
      placeholder="Search activities..."
    />
  )}
  renderGroup={(params) => (
    <li key={params.key}>
      <GroupHeader>{params.group}</GroupHeader>
      <GroupItems>{params.children}</GroupItems>
    </li>
  )}
/>

Mobile Responsiveness

Responsive Goal Card

<Card sx={{ 
  p: { xs: 1.5, sm: 2 },
  '& .MuiTypography-h6': {
    fontSize: { xs: '1rem', sm: '1.25rem' }
  }
}}>
  <Stack spacing={{ xs: 1, sm: 2 }}>
    {/* Content adapts to screen size */}
  </Stack>
</Card>

Mobile Navigation

<BottomNavigation value={activeTab} onChange={handleTabChange}>
  <BottomNavigationAction label="Active" icon={<TrendingUpIcon />} />
  <BottomNavigationAction label="Completed" icon={<CheckCircleIcon />} />
  <BottomNavigationAction label="All Goals" icon={<ListIcon />} />
</BottomNavigation>

Loading States

Skeleton Loading

const GoalCardSkeleton = () => (
  <Card sx={{ p: 2 }}>
    <Skeleton variant="text" width="60%" height={32} />
    <Skeleton variant="text" width="100%" height={20} />
    <Skeleton variant="rectangular" width="100%" height={8} sx={{ my: 2 }} />
    <Box display="flex" justifyContent="space-between">
      <Skeleton variant="circular" width={32} height={32} />
      <Skeleton variant="text" width={100} />
    </Box>
  </Card>
);

Progressive Loading

{loading ? (
  <Grid container spacing={2}>
    {[...Array(6)].map((_, i) => (
      <Grid item xs={12} sm={6} md={4} key={i}>
        <GoalCardSkeleton />
      </Grid>
    ))}
  </Grid>
) : (
  <GoalsList goals={goals} />
)}

Empty States

No Goals

<EmptyState
  icon={<TargetIcon sx={{ fontSize: 64 }} />}
  title="No goals yet"
  description="Set your first goal to start tracking your progress"
  action={
    <Button
      variant="contained"
      startIcon={<AddIcon />}
      onClick={handleCreateGoal}
    >
      Create Your First Goal
    </Button>
  }
/>

Error Handling

Error States

<Alert severity="error" sx={{ mb: 2 }}>
  <AlertTitle>Unable to load goals</AlertTitle>
  Please check your connection and try again.
  <Button size="small" onClick={retry} sx={{ mt: 1 }}>
    Retry
  </Button>
</Alert>

Accessibility

ARIA Labels

<IconButton
  aria-label="Edit goal"
  onClick={handleEdit}
>
  <EditIcon />
</IconButton>
 
<LinearProgress
  aria-label={`Goal progress: ${progressPercentage}%`}
  aria-valuenow={progressPercentage}
  aria-valuemin={0}
  aria-valuemax={100}
/>

Keyboard Navigation

  • Tab through interactive elements
  • Enter/Space to activate buttons
  • Escape to close modals
  • Arrow keys in selection lists

Animation Guidelines

Progress Updates

<LinearProgress
  variant="determinate"
  value={progressPercentage}
  sx={{
    '& .MuiLinearProgress-bar': {
      transition: 'transform 0.4s ease-in-out'
    }
  }}
/>

Status Changes

<Fade in={showStatusChange} timeout={300}>
  <Alert severity="success">
    Goal status updated to {newStatus}
  </Alert>
</Fade>

Dark Mode Support

const GoalCard = styled(Card)(({ theme }) => ({
  backgroundColor: theme.palette.mode === 'dark' 
    ? theme.palette.grey[900] 
    : theme.palette.background.paper,
  '&:hover': {
    backgroundColor: theme.palette.mode === 'dark'
      ? theme.palette.grey[800]
      : theme.palette.grey[50]
  }
}));

Component Library

Reusable Goal Components

// Export from @/components/goals/
export { GoalCard } from './GoalCard';
export { GoalProgressBar } from './GoalProgressBar';
export { GoalStatusChip } from './GoalStatusChip';
export { GoalCreationWizard } from './GoalCreationWizard';
export { GoalDetailsPanel } from './GoalDetailsPanel';
export { GoalActivityPicker } from './GoalActivityPicker';
export { GoalDateRangePicker } from './GoalDateRangePicker';
export { GoalAssigneeSelector } from './GoalAssigneeSelector';

Best Practices

  1. Consistent Spacing: Use theme spacing units (multiples of 8px)
  2. Color Usage: Use semantic colors from theme palette
  3. Typography: Follow established type hierarchy
  4. Icons: Use Material Icons consistently
  5. Feedback: Provide immediate visual feedback for all actions
  6. Performance: Virtualize long lists, lazy load charts
  7. Responsive: Test on all screen sizes
  8. Accessibility: Include proper ARIA labels and keyboard support

For more UI guidelines, refer to the Material-UI documentation (opens in a new tab) and the main UI Guidelines section.