utils.ts
1 import { clsx, type ClassValue } from 'clsx'; 2 import { twMerge } from 'tailwind-merge'; 3 import type { ReviewHistoryEntry } from './types'; 4 5 export function cn(...inputs: ClassValue[]) { 6 return twMerge(clsx(inputs)); 7 } 8 9 export function formatDuration(ms: number): string { 10 const totalSeconds = Math.round(ms / 1000); 11 const minutes = Math.floor(totalSeconds / 60); 12 const seconds = totalSeconds % 60; 13 if (minutes > 0) return `${minutes}m ${seconds}s`; 14 return `${seconds}s`; 15 } 16 17 export function formatBytes(bytes: number): string { 18 if (bytes < 1024) return `${bytes}B`; 19 if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`; 20 return `${(bytes / (1024 * 1024)).toFixed(1)}MB`; 21 } 22 23 export function timeAgo(iso: string): string { 24 const diff = Date.now() - new Date(iso).getTime(); 25 const mins = Math.floor(diff / 60_000); 26 const hours = Math.floor(diff / 3_600_000); 27 const days = Math.floor(diff / 86_400_000); 28 if (mins < 1) return 'just now'; 29 if (mins < 60) return `${mins}m ago`; 30 if (hours < 24) return `${hours}h ago`; 31 if (days < 7) return `${days}d ago`; 32 return new Date(iso).toLocaleDateString(); 33 } 34 35 export function parseRepoRef(prUrl: string): string { 36 const match = prUrl.match(/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)/); 37 return match ? `${match[1]}#${match[2]}` : prUrl; 38 } 39 40 export interface PRGroup { 41 prUrl: string; 42 repoRef: string; 43 prTitle: string; 44 author: string; 45 latestReview: ReviewHistoryEntry; 46 reviews: ReviewHistoryEntry[]; 47 } 48 49 export function groupReviewsByPR(history: ReviewHistoryEntry[]): PRGroup[] { 50 const map = new Map<string, ReviewHistoryEntry[]>(); 51 for (const entry of history) { 52 const list = map.get(entry.prUrl); 53 if (list) list.push(entry); 54 else map.set(entry.prUrl, [entry]); 55 } 56 57 const groups: PRGroup[] = []; 58 for (const [prUrl, reviews] of map) { 59 reviews.sort((a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime()); 60 const latest = reviews[0]; 61 groups.push({ 62 prUrl, 63 repoRef: parseRepoRef(prUrl), 64 prTitle: latest.prTitle, 65 author: latest.author, 66 latestReview: latest, 67 reviews, 68 }); 69 } 70 71 groups.sort((a, b) => new Date(b.latestReview.savedAt).getTime() - new Date(a.latestReview.savedAt).getTime()); 72 return groups; 73 }