import React, { useCallback, useEffect, useState } from 'react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import {
AlertCircle,
ChevronDown,
CircleDot,
Clock,
ExternalLink,
FileCode,
FileText,
Loader2,
MessageCircle,
RefreshCw,
} from 'lucide-react';
import { api } from '@/api';
/* ─── Priority lanes ─────────────────────────────────────────────────────── */
const PRIORITY_LANES = [
{ key: 'critical', emoji: '🔴', label: 'CRITICAL', borderColor: 'border-t-red-500', textColor: 'text-red-500', badgeClass: 'bg-red-500/15 text-red-500 border-red-500/20' },
{ key: 'high', emoji: '🟠', label: 'HIGH', borderColor: 'border-t-orange-500', textColor: 'text-orange-500', badgeClass: 'bg-orange-500/15 text-orange-500 border-orange-500/20' },
{ key: 'medium', emoji: '🟡', label: 'MEDIUM', borderColor: 'border-t-yellow-500', textColor: 'text-yellow-500', badgeClass: 'bg-yellow-500/15 text-yellow-500 border-yellow-500/20' },
{ key: 'low', emoji: '🔵', label: 'LOW', borderColor: 'border-t-blue-500', textColor: 'text-blue-500', badgeClass: 'bg-blue-500/15 text-blue-500 border-blue-500/20' },
{ key: 'niceToHave', emoji: '💭', label: 'NICE TO HAVE', borderColor: 'border-t-border', textColor: 'text-muted-foreground', badgeClass: 'bg-muted/50 text-muted-foreground border-border/50' },
];
/* ─── Helpers ────────────────────────────────────────────────────────────── */
function priorityFromLabels(labels = []) {
for (const l of labels) {
if (l.name === 'priority:critical') return 'critical';
if (l.name === 'priority:high') return 'high';
if (l.name === 'priority:medium') return 'medium';
if (l.name === 'priority:low') return 'low';
if (l.name === 'priority:nice-to-have') return 'niceToHave';
}
return 'low';
}
function cleanTitle(title) {
return title.replace(/^(CRITICAL|HIGH|MEDIUM|LOW|NICE[\s-]TO[\s-]HAVE)\s*:\s*/i, '').trim();
}
function stripMarkdown(text) {
if (!text) return '';
return text
.replace(/#{1,6}\s+[^\n]*/g, '')
.replace(/```[\s\S]*?```/g, '')
.replace(/`([^`]+)`/g, '$1')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
.replace(/^[-*+]\s+/gm, '')
.replace(/\n\n+/g, ' ')
.replace(/\n/g, ' ')
.replace(/\s{2,}/g, ' ')
.trim();
}
function timeAgo(dateStr) {
const diff = Date.now() - new Date(dateStr).getTime();
const mins = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (mins < 1) return 'just now';
if (mins < 60) return `${mins}m ago`;
if (hours < 24) return `${hours}h ago`;
if (days < 30) return `${days}d ago`;
return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
function labelStyle(hex) {
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return {
backgroundColor: `rgba(${r},${g},${b},0.12)`,
color: `#${hex}`,
border: `1px solid rgba(${r},${g},${b},0.28)`,
};
}
/* ─── Label chip ─────────────────────────────────────────────────────────── */
function LabelChip({ label }) {
return (
{label.name}
);
}
/* ─── Issue card ─────────────────────────────────────────────────────────── */
function IssueCard({ issue }) {
const typeLabels = issue.labels || [];
const title = cleanTitle(issue.title);
const preview = stripMarkdown(issue.body);
return (
{/* Title row */}
{title}
{preview}
{lane.label}
{items.length}
Agents
Files Modified
{file}
))}
Work Completed
Open issues ·{' '} BillTracker ↗
{roadmapError}
Failed to load activity log
{devLogError}