import { useState, useEffect, useRef } from "react" import { Pause, Play } from "lucide-react" import { Button } from "@/app/_components/ui/button" import { cn } from "@/app/_lib/utils" import { Slider } from "@/app/_components/ui/slider" import { getMonthName } from "@/app/_utils/common" interface CrimeTimelapseProps { startYear: number endYear: number onChange?: (year: number, month: number, progress: number) => void onPlayingChange?: (isPlaying: boolean) => void className?: string autoPlay?: boolean autoPlaySpeed?: number // Time to progress through one month in ms } export function CrimeTimelapse({ startYear = 2020, endYear = 2024, onChange, onPlayingChange, className, autoPlay = true, autoPlaySpeed = 1000, // Speed of month progress }: CrimeTimelapseProps) { const [currentYear, setCurrentYear] = useState(startYear) const [currentMonth, setCurrentMonth] = useState(1) // Start at January (1) const [progress, setProgress] = useState(0) // Progress within the current month const [isPlaying, setIsPlaying] = useState(autoPlay) const [isDragging, setIsDragging] = useState(false) const animationRef = useRef(null) const lastUpdateTimeRef = useRef(0) // Notify parent about playing state changes useEffect(() => { if (onPlayingChange) { onPlayingChange(isPlaying || isDragging) } }, [isPlaying, isDragging, onPlayingChange]) // Calculate total months from start to end year const totalMonths = ((endYear - startYear) * 12) + 12 // +12 to include all months of end year const calculateOverallProgress = (): number => { const yearDiff = currentYear - startYear const monthProgress = (yearDiff * 12) + (currentMonth - 1) return ((monthProgress + progress) / (totalMonths - 1)) * 100 } const calculateTimeFromProgress = (overallProgress: number): { year: number; month: number; progress: number } => { const totalProgress = (overallProgress * (totalMonths - 1)) / 100 const monthsFromStart = Math.floor(totalProgress) const year = startYear + Math.floor(monthsFromStart / 12) const month = (monthsFromStart % 12) + 1 // 1-12 for months const monthProgress = totalProgress - Math.floor(totalProgress) return { year: Math.min(year, endYear), month: Math.min(month, 12), progress: monthProgress } } // Calculate the current position for the active marker const calculateMarkerPosition = (): string => { const overallProgress = calculateOverallProgress() return `${overallProgress}%` } const animate = (timestamp: number) => { if (!lastUpdateTimeRef.current) { lastUpdateTimeRef.current = timestamp } if (!isDragging) { const elapsed = timestamp - lastUpdateTimeRef.current const progressIncrement = elapsed / autoPlaySpeed let newProgress = progress + progressIncrement let newMonth = currentMonth let newYear = currentYear if (newProgress >= 1) { newProgress = 0 newMonth = currentMonth + 1 if (newMonth > 12) { newMonth = 1 newYear = currentYear + 1 if (newYear > endYear) { newYear = startYear newMonth = 1 } } setCurrentMonth(newMonth) setCurrentYear(newYear) } setProgress(newProgress) if (onChange) { onChange(newYear, newMonth, newProgress) } lastUpdateTimeRef.current = timestamp } if (isPlaying) { animationRef.current = requestAnimationFrame(animate) } } useEffect(() => { if (isPlaying) { lastUpdateTimeRef.current = 0 animationRef.current = requestAnimationFrame(animate) } else if (animationRef.current) { cancelAnimationFrame(animationRef.current) } return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current) } } }, [isPlaying, currentYear, currentMonth, progress, isDragging]) const handlePlayPause = () => { const newPlayingState = !isPlaying setIsPlaying(newPlayingState) if (onPlayingChange) { onPlayingChange(newPlayingState) } } const handleSliderChange = (value: number[]) => { const overallProgress = value[0] const { year, month, progress } = calculateTimeFromProgress(overallProgress) setCurrentYear(year) setCurrentMonth(month) setProgress(progress) if (onChange) { onChange(year, month, progress) } } const handleSliderDragStart = () => { setIsDragging(true) if (onPlayingChange) { onPlayingChange(true) // Treat dragging as a form of "playing" for performance optimization } } const handleSliderDragEnd = () => { setIsDragging(false) if (onPlayingChange) { onPlayingChange(isPlaying) // Restore to actual playing state } } // Create year markers const yearMarkers = [] for (let year = startYear; year <= endYear; year++) { yearMarkers.push(year) } return (
{isPlaying && (
{getMonthName(currentMonth)} {currentYear}
)} {/* Wrap button and slider in their container */}
{/* Play/Pause button */} {/* Slider */}
{/* Year markers */}
{yearMarkers.map((year, index) => (
{year}
))}
) }