/ lib / utils.ts
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  }