All files / src/hooks timerUtils.ts

100% Statements 36/36
100% Branches 14/14
100% Functions 7/7
100% Lines 34/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                          17x 39x 39x 59x 39x   35x   35x 2x 2x 2x 2x     33x                         12x 12x 9x 9x                         28x 28x   28x           28x   84x 22x 22x 22x 22x 21x 21x         28x               21x 21x 21x   21x 4x   17x    
import type { MutableRefObject, Dispatch, SetStateAction } from 'react'
import type { Timer } from './useTimers'
 
/**
 * Creates a timer tick function that decrements the remaining time
 * and handles completion. Used by both addTimer and startTimer.
 */
export function createTimerTick(
  id: string,
  intervalsRef: MutableRefObject<Map<string, number>>,
  onComplete: MutableRefObject<((timer: Timer) => void) | undefined>,
  setTimers: Dispatch<SetStateAction<Timer[]>>
): () => void {
  return () => {
    setTimers((prev) =>
      prev.map((timer) => {
        if (timer.id !== id) return timer
        if (!timer.isRunning) return timer
 
        const newRemaining = timer.remaining - 1
 
        if (newRemaining <= 0) {
          clearInterval(intervalsRef.current.get(id))
          intervalsRef.current.delete(id)
          onComplete.current?.({ ...timer, remaining: 0, isRunning: false })
          return { ...timer, remaining: 0, isRunning: false }
        }
 
        return { ...timer, remaining: newRemaining }
      })
    )
  }
}
 
/**
 * Clears an interval for a timer and removes it from the map.
 */
export function clearTimerInterval(
  id: string,
  intervalsRef: MutableRefObject<Map<string, number>>
): void {
  const intervalId = intervalsRef.current.get(id)
  if (intervalId) {
    clearInterval(intervalId)
    intervalsRef.current.delete(id)
  }
}
 
/**
 * Detects time mentions in text and returns durations in seconds.
 *
 * Supports patterns like:
 * - "15 minutes", "15 min", "15m"
 * - "2 hours", "2 hr", "2h"
 * - "30 seconds", "30 sec", "30s"
 */
export function detectTimes(text: string): number[] {
  const times: number[] = []
  const seen = new Set<string>()
 
  const patterns = [
    { regex: /(\d+)\s*(?:hours?|hrs?|h)\b/gi, multiplier: 3600 },
    { regex: /(\d+)\s*(?:minutes?|mins?|m)\b/gi, multiplier: 60 },
    { regex: /(\d+)\s*(?:seconds?|secs?|s)\b/gi, multiplier: 1 },
  ]
 
  for (const { regex, multiplier } of patterns) {
    let match
    while ((match = regex.exec(text)) !== null) {
      const value = parseInt(match[1], 10)
      const seconds = value * multiplier
      const key = `${match.index}-${value}-${multiplier}`
      if (!seen.has(key) && seconds > 0) {
        seen.add(key)
        times.push(seconds)
      }
    }
  }
 
  return times
}
 
/**
 * Formats seconds as a human-readable time string.
 * e.g., 90 -> "1:30", 3661 -> "1:01:01"
 */
export function formatTimerDisplay(seconds: number): string {
  const hrs = Math.floor(seconds / 3600)
  const mins = Math.floor((seconds % 3600) / 60)
  const secs = seconds % 60
 
  if (hrs > 0) {
    return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
  }
  return `${mins}:${secs.toString().padStart(2, '0')}`
}
 
← Back to Dashboard