Skip to content

⏱️ Time Estimate Display System

Purpose

Board directors and executives need to know exactly how long tasks will take. GetCimple's time estimate system provides:

  • Clear time commitments upfront
  • Role-specific estimates (board vs management)
  • Real-time countdown during tasks
  • Historical accuracy tracking

Time Estimate Categories

Board Tasks (2-5 minutes)

  • Strategic decisions
  • Risk appetite settings
  • Target approvals
  • Compliance sign-offs

Management Tasks (15-30 minutes)

  • Detailed assessments
  • Evidence uploads
  • Process documentation
  • Implementation planning

IT/Technical Tasks (30-60 minutes)

  • Technical configurations
  • Security assessments
  • System integrations
  • Detailed implementations

Component Specifications

Basic Time Estimate Badge

<div
  class="gc-time-estimate"
  role="status"
  aria-label="Estimated time: 3 minutes"
>
  <svg class="gc-time-estimate__icon" aria-hidden="true">
    <use href="#icon-clock"></use>
  </svg>
  <span class="gc-time-estimate__value">3</span>
  <span class="gc-time-estimate__unit">min</span>
</div>
.gc-time-estimate {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 12px;
  background: var(--gc-bg-time);
  border-radius: 20px;
  font-size: 14px;
  font-weight: 500;
}

.gc-time-estimate__icon {
  width: 16px;
  height: 16px;
  color: var(--gc-text-secondary);
}

.gc-time-estimate__value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--gc-text-primary);
}

.gc-time-estimate__unit {
  color: var(--gc-text-secondary);
}

/* Role-based coloring */
.gc-time-estimate--board {
  background: #eff6ff; /* Light blue */
  color: #1e40af;
}

.gc-time-estimate--management {
  background: #fef3c7; /* Light amber */
  color: #92400e;
}

.gc-time-estimate--technical {
  background: #f3f4f6; /* Light gray */
  color: #374151;
}

Detailed Time Breakdown

<div class="gc-time-breakdown">
  <h3 class="gc-time-breakdown__title">Time Investment</h3>

  <div class="gc-time-breakdown__total">
    <span class="gc-time-breakdown__total-value">8</span>
    <span class="gc-time-breakdown__total-unit">minutes total</span>
  </div>

  <ul class="gc-time-breakdown__list">
    <li class="gc-time-breakdown__item gc-time-breakdown__item--board">
      <span class="gc-time-breakdown__role">Board Review</span>
      <span class="gc-time-breakdown__time">3 min</span>
      <span class="gc-time-breakdown__status">Required</span>
    </li>

    <li class="gc-time-breakdown__item gc-time-breakdown__item--management">
      <span class="gc-time-breakdown__role">Management Input</span>
      <span class="gc-time-breakdown__time">5 min</span>
      <span class="gc-time-breakdown__status">Completed</span>
    </li>
  </ul>
</div>

Active Timer Component

<div class="gc-timer" data-duration="180" role="timer" aria-live="polite">
  <div class="gc-timer__display">
    <span class="gc-timer__minutes">2</span>
    <span class="gc-timer__separator">:</span>
    <span class="gc-timer__seconds">47</span>
  </div>

  <div
    class="gc-timer__progress"
    role="progressbar"
    aria-valuenow="33"
    aria-valuemin="0"
    aria-valuemax="180"
  >
    <div class="gc-timer__progress-bar" style="width: 18.3%"></div>
  </div>

  <div class="gc-timer__controls">
    <button class="gc-timer__pause" aria-label="Pause timer">
      <svg><use href="#icon-pause"></use></svg>
    </button>
    <button class="gc-timer__reset" aria-label="Reset timer">
      <svg><use href="#icon-reset"></use></svg>
    </button>
  </div>
</div>
class Timer {
  constructor(element) {
    this.element = element
    this.duration = parseInt(element.dataset.duration)
    this.remaining = this.duration
    this.isRunning = false
    this.interval = null

    this.minutesEl = element.querySelector('.gc-timer__minutes')
    this.secondsEl = element.querySelector('.gc-timer__seconds')
    this.progressBar = element.querySelector('.gc-timer__progress-bar')

    this.init()
  }

  init() {
    this.render()
    this.bindEvents()
    this.start()
  }

  start() {
    if (this.isRunning) return

    this.isRunning = true
    this.interval = setInterval(() => this.tick(), 1000)
    this.element.classList.add('gc-timer--running')
  }

  pause() {
    this.isRunning = false
    clearInterval(this.interval)
    this.element.classList.remove('gc-timer--running')
  }

  reset() {
    this.pause()
    this.remaining = this.duration
    this.render()
  }

  tick() {
    if (this.remaining > 0) {
      this.remaining--
      this.render()

      // Warnings at thresholds
      if (this.remaining === 60) {
        this.element.classList.add('gc-timer--warning')
        this.announce('1 minute remaining')
      } else if (this.remaining === 0) {
        this.complete()
      }
    }
  }

  render() {
    const minutes = Math.floor(this.remaining / 60)
    const seconds = this.remaining % 60

    this.minutesEl.textContent = minutes
    this.secondsEl.textContent = seconds.toString().padStart(2, '0')

    const progress = ((this.duration - this.remaining) / this.duration) * 100
    this.progressBar.style.width = `${progress}%`

    // Update ARIA
    this.element.setAttribute('aria-valuenow', this.duration - this.remaining)
  }

  complete() {
    this.pause()
    this.element.classList.add('gc-timer--complete')
    this.announce('Time complete')
    this.onComplete?.()
  }

  announce(message) {
    // Screen reader announcement
    const announcement = document.createElement('div')
    announcement.className = 'sr-only'
    announcement.setAttribute('role', 'status')
    announcement.textContent = message
    document.body.appendChild(announcement)
    setTimeout(() => announcement.remove(), 1000)
  }

  bindEvents() {
    this.element
      .querySelector('.gc-timer__pause')
      ?.addEventListener('click', () => {
        this.isRunning ? this.pause() : this.start()
      })

    this.element
      .querySelector('.gc-timer__reset')
      ?.addEventListener('click', () => this.reset())
  }
}

Time Estimate Cards

<!-- Quick Task Card -->
<article class="gc-task-card gc-task-card--quick">
  <header class="gc-task-card__header">
    <h3 class="gc-task-card__title">Review Compliance Status</h3>
    <div class="gc-time-estimate gc-time-estimate--board">
      <svg class="gc-time-estimate__icon">
        <use href="#icon-lightning"></use>
      </svg>
      <span>Quick: 2 min</span>
    </div>
  </header>

  <div class="gc-task-card__body">
    <p class="gc-task-card__description">
      Review quarterly compliance dashboard and approve continued approach
    </p>

    <div class="gc-task-card__meta">
      <span class="gc-task-card__role">Board Decision</span>
      <span class="gc-task-card__frequency">Quarterly</span>
    </div>
  </div>

  <footer class="gc-task-card__footer">
    <button class="gc-task-card__action">
      Start Review
      <span class="gc-task-card__time-hint">(2 min)</span>
    </button>
  </footer>
</article>

<!-- Detailed Task Card -->
<article class="gc-task-card gc-task-card--detailed">
  <header class="gc-task-card__header">
    <h3 class="gc-task-card__title">Complete E8 Assessment</h3>
    <div class="gc-time-breakdown gc-time-breakdown--inline">
      <span class="gc-time-breakdown__segment"> IT: 15 min </span>
      <span class="gc-time-breakdown__segment"> Board: 3 min </span>
    </div>
  </header>

  <div class="gc-task-card__progress">
    <div class="gc-task-card__progress-header">
      <span>IT Team Progress</span>
      <span>90% Complete</span>
    </div>
    <div class="gc-task-card__progress-bar">
      <div class="gc-task-card__progress-fill" style="width: 90%"></div>
    </div>
    <p class="gc-task-card__progress-note">Board input needed to finalize</p>
  </div>

  <footer class="gc-task-card__footer">
    <button class="gc-task-card__action">
      Complete Board Portion
      <span class="gc-task-card__time-hint">(3 min)</span>
    </button>
  </footer>
</article>

Historical Accuracy Display

<div class="gc-time-accuracy">
  <h4 class="gc-time-accuracy__title">Time Estimate Accuracy</h4>

  <div class="gc-time-accuracy__stats">
    <div class="gc-time-accuracy__stat">
      <span class="gc-time-accuracy__value">95%</span>
      <span class="gc-time-accuracy__label">On Time</span>
    </div>

    <div class="gc-time-accuracy__stat">
      <span class="gc-time-accuracy__value">2.8 min</span>
      <span class="gc-time-accuracy__label">Avg Board Time</span>
    </div>

    <div class="gc-time-accuracy__stat">
      <span class="gc-time-accuracy__value">18 min</span>
      <span class="gc-time-accuracy__label">Avg Mgmt Time</span>
    </div>
  </div>

  <div class="gc-time-accuracy__chart">
    <!-- Sparkline or small chart showing trend -->
  </div>
</div>

Visual States

Timer States

/* Default/Idle */
.gc-timer {
  background: var(--gc-bg-card);
  border: 2px solid var(--gc-border-default);
}

/* Running */
.gc-timer--running {
  border-color: var(--gc-info);
  animation: pulse-border 2s infinite;
}

/* Warning (< 1 minute) */
.gc-timer--warning {
  border-color: var(--gc-status-warning);
  background: #fef3c7;
}

.gc-timer--warning .gc-timer__display {
  color: var(--gc-status-warning);
  animation: blink 1s infinite;
}

/* Complete */
.gc-timer--complete {
  border-color: var(--gc-status-good);
  background: #d1fae5;
}

/* Overtime */
.gc-timer--overtime {
  border-color: var(--gc-status-critical);
  background: #fee2e2;
}

@keyframes pulse-border {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.6;
  }
}

@keyframes blink {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

Time Badge Variants

/* Ultra-quick (< 1 min) */
.gc-time-estimate--instant {
  background: #d1fae5;
  color: #065f46;
}

.gc-time-estimate--instant::before {
  content: '⚑';
  margin-right: 4px;
}

/* Quick (1-5 min) */
.gc-time-estimate--quick {
  background: #dbeafe;
  color: #1e40af;
}

/* Moderate (5-15 min) */
.gc-time-estimate--moderate {
  background: #fef3c7;
  color: #92400e;
}

/* Extended (15-30 min) */
.gc-time-estimate--extended {
  background: #f3f4f6;
  color: #374151;
}

/* Long (30+ min) */
.gc-time-estimate--long {
  background: #fee2e2;
  color: #991b1b;
}

Smart Time Features

Adaptive Estimates

class AdaptiveTimeEstimate {
  constructor(taskType, userRole) {
    this.taskType = taskType
    this.userRole = userRole
    this.history = this.loadHistory()
  }

  getEstimate() {
    const baseTime = this.getBaseTime()
    const adjustment = this.getAdjustment()

    return {
      estimate: baseTime + adjustment,
      confidence: this.getConfidence(),
      range: this.getRange(),
    }
  }

  getBaseTime() {
    // Base times by task type and role
    const times = {
      'board-decision': { board: 3, executive: 5 },
      'compliance-review': { board: 5, executive: 15 },
      assessment: { board: 2, management: 20, technical: 45 },
    }

    return times[this.taskType]?.[this.userRole] || 10
  }

  getAdjustment() {
    // Adjust based on user's historical performance
    if (this.history.length < 5) return 0

    const avgTime =
      this.history.reduce((sum, h) => sum + h.actual, 0) / this.history.length
    const avgEstimate =
      this.history.reduce((sum, h) => sum + h.estimate, 0) / this.history.length

    return avgTime - avgEstimate
  }

  getConfidence() {
    // Higher confidence with more history
    if (this.history.length >= 20) return 'high'
    if (this.history.length >= 10) return 'medium'
    return 'low'
  }

  getRange() {
    // Provide a time range based on confidence
    const estimate = this.getEstimate().estimate
    const variance = this.getConfidence() === 'high' ? 0.1 : 0.25

    return {
      min: Math.floor(estimate * (1 - variance)),
      max: Math.ceil(estimate * (1 + variance)),
    }
  }
}

Batch Time Calculation

class BatchTimeCalculator {
  constructor(tasks) {
    this.tasks = tasks
  }

  calculate() {
    const grouped = this.groupByRole()
    const times = {}

    for (const [role, tasks] of Object.entries(grouped)) {
      times[role] = {
        sequential: this.getSequentialTime(tasks),
        batched: this.getBatchedTime(tasks),
        savings: 0,
      }

      times[role].savings = times[role].sequential - times[role].batched
    }

    return times
  }

  groupByRole() {
    return this.tasks.reduce((groups, task) => {
      const role = task.assignedRole
      groups[role] = groups[role] || []
      groups[role].push(task)
      return groups
    }, {})
  }

  getSequentialTime(tasks) {
    return tasks.reduce((total, task) => total + task.estimatedTime, 0)
  }

  getBatchedTime(tasks) {
    // Context switching overhead removed when batching
    const setupTime = 2 // Fixed setup time
    const perTaskTime = tasks.reduce(
      (total, task) => total + task.estimatedTime * 0.8,
      0
    ) // 20% efficiency gain

    return setupTime + perTaskTime
  }
}

Implementation Guidelines

React Components

// TimeEstimate Component
export const TimeEstimate: React.FC<{
  minutes: number
  role?: 'board' | 'management' | 'technical'
  showIcon?: boolean
}> = ({ minutes, role = 'board', showIcon = true }) => {
  const getCategory = () => {
    if (minutes <= 1) return 'instant'
    if (minutes <= 5) return 'quick'
    if (minutes <= 15) return 'moderate'
    if (minutes <= 30) return 'extended'
    return 'long'
  }

  return (
    <div
      className={`gc-time-estimate gc-time-estimate--${role} gc-time-estimate--${getCategory()}`}
    >
      {showIcon && <ClockIcon />}
      <span className="gc-time-estimate__value">{minutes}</span>
      <span className="gc-time-estimate__unit">min</span>
    </div>
  )
}

// Timer Hook
export const useTimer = (duration: number) => {
  const [remaining, setRemaining] = useState(duration)
  const [isRunning, setIsRunning] = useState(false)

  useEffect(() => {
    if (!isRunning || remaining <= 0) return

    const interval = setInterval(() => {
      setRemaining((r) => r - 1)
    }, 1000)

    return () => clearInterval(interval)
  }, [isRunning, remaining])

  return {
    remaining,
    isRunning,
    progress: ((duration - remaining) / duration) * 100,
    start: () => setIsRunning(true),
    pause: () => setIsRunning(false),
    reset: () => {
      setRemaining(duration)
      setIsRunning(false)
    },
  }
}

Accessibility Considerations

Screen Reader Announcements

  • Announce time estimates when cards receive focus
  • Provide periodic updates during active timers (every 30 seconds)
  • Announce warnings at 1 minute and completion

Keyboard Controls

  • Space/Enter to start/pause timer
  • Escape to reset timer
  • Tab navigation through controls

Visual Indicators

  • Use color AND icons for time categories
  • Provide text labels alongside visual indicators
  • Ensure sufficient contrast for all states

Testing Checklist

  • Time estimates display correctly for all roles
  • Timer counts down accurately
  • Progress bars update smoothly
  • Warnings appear at correct thresholds
  • Screen reader announcements work
  • Keyboard controls function properly
  • Historical accuracy tracking works
  • Adaptive estimates adjust appropriately
  • Batch calculations show savings
  • Mobile display remains clear

Conclusion

The Time Estimate Display System ensures transparency and sets clear expectations for all users, particularly time-pressed board directors. By showing accurate, role-specific time commitments upfront, GetCimple enables better planning and higher completion rates.