Skip to content

Commit 6b4e1e8

Browse files
sallyomclaude
andcommitted
feat(frontend): Match RFE workflow UI pattern in BugFix workspaces
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: sallyom <somalley@redhat.com>
1 parent c01c0a2 commit 6b4e1e8

File tree

5 files changed

+511
-311
lines changed

5 files changed

+511
-311
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
'use client';
2+
3+
import Link from 'next/link';
4+
import { Button } from '@/components/ui/button';
5+
import { Badge } from '@/components/ui/badge';
6+
import { ArrowLeft, Loader2, Trash2, Bug, ExternalLink, GitBranch, Clock } from 'lucide-react';
7+
import { formatDistanceToNow } from 'date-fns';
8+
9+
type BugFixWorkflow = {
10+
id: string;
11+
title: string;
12+
description?: string;
13+
githubIssueNumber: number;
14+
githubIssueURL: string;
15+
branchName: string;
16+
phase: string;
17+
createdAt?: string;
18+
};
19+
20+
type BugFixHeaderProps = {
21+
workflow: BugFixWorkflow;
22+
projectName: string;
23+
deleting: boolean;
24+
onDelete: () => Promise<void>;
25+
};
26+
27+
const getPhaseColor = (phase: string) => {
28+
switch (phase) {
29+
case 'Ready':
30+
case 'Completed':
31+
return 'bg-green-500/10 text-green-500 border-green-500/20';
32+
case 'Running':
33+
return 'bg-blue-500/10 text-blue-500 border-blue-500/20';
34+
case 'Initializing':
35+
case 'Pending':
36+
return 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20';
37+
case 'Failed':
38+
return 'bg-red-500/10 text-red-500 border-red-500/20';
39+
default:
40+
return 'bg-gray-500/10 text-gray-500 border-gray-500/20';
41+
}
42+
};
43+
44+
export function BugFixHeader({ workflow, projectName, deleting, onDelete }: BugFixHeaderProps) {
45+
return (
46+
<div className="space-y-4">
47+
<div className="flex items-start justify-between">
48+
<div className="flex items-center gap-4">
49+
<Link href={`/projects/${encodeURIComponent(projectName)}/bugfix`}>
50+
<Button variant="ghost" size="sm">
51+
<ArrowLeft className="h-4 w-4 mr-2" />
52+
Back to BugFix Workspaces
53+
</Button>
54+
</Link>
55+
</div>
56+
<Button variant="destructive" size="sm" onClick={onDelete} disabled={deleting}>
57+
{deleting ? (
58+
<>
59+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
60+
Deleting…
61+
</>
62+
) : (
63+
<>
64+
<Trash2 className="mr-2 h-4 w-4" />
65+
Delete Workspace
66+
</>
67+
)}
68+
</Button>
69+
</div>
70+
71+
<div className="flex items-start justify-between">
72+
<div className="flex-1">
73+
<div className="flex items-center gap-3">
74+
<Bug className="h-6 w-6" />
75+
<h1 className="text-3xl font-bold">{workflow.title}</h1>
76+
</div>
77+
<div className="flex items-center gap-4 mt-2 text-sm text-muted-foreground">
78+
<a
79+
href={workflow.githubIssueURL}
80+
target="_blank"
81+
rel="noopener noreferrer"
82+
className="flex items-center gap-1 hover:text-primary"
83+
>
84+
GitHub Issue #{workflow.githubIssueNumber}
85+
<ExternalLink className="h-3 w-3" />
86+
</a>
87+
<div className="flex items-center gap-1">
88+
<GitBranch className="h-3 w-3" />
89+
<span className="font-mono text-xs">{workflow.branchName}</span>
90+
</div>
91+
{workflow.createdAt && (
92+
<div className="flex items-center gap-1">
93+
<Clock className="h-3 w-3" />
94+
{formatDistanceToNow(new Date(workflow.createdAt), { addSuffix: true })}
95+
</div>
96+
)}
97+
</div>
98+
</div>
99+
<Badge variant="outline" className={getPhaseColor(workflow.phase)}>
100+
{workflow.phase}
101+
</Badge>
102+
</div>
103+
</div>
104+
);
105+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
'use client';
2+
3+
import { Badge } from '@/components/ui/badge';
4+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
5+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
6+
import { Loader2 } from 'lucide-react';
7+
import { formatDistanceToNow } from 'date-fns';
8+
import SessionSelector from '@/components/workspaces/bugfix/SessionSelector';
9+
import { useRouter } from 'next/navigation';
10+
11+
type BugFixSession = {
12+
id: string;
13+
title: string;
14+
sessionType: string;
15+
phase: string;
16+
createdAt: string;
17+
completedAt?: string;
18+
description?: string;
19+
error?: string;
20+
};
21+
22+
type BugFixSessionsTableProps = {
23+
sessions: BugFixSession[];
24+
projectName: string;
25+
workflowId: string;
26+
workflow: {
27+
githubIssueNumber: number;
28+
phase: string;
29+
};
30+
sessionsLoading: boolean;
31+
};
32+
33+
const getPhaseColor = (phase: string) => {
34+
switch (phase) {
35+
case 'Ready':
36+
case 'Completed':
37+
return 'bg-green-500/10 text-green-500 border-green-500/20';
38+
case 'Running':
39+
return 'bg-blue-500/10 text-blue-500 border-blue-500/20';
40+
case 'Initializing':
41+
case 'Pending':
42+
return 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20';
43+
case 'Failed':
44+
return 'bg-red-500/10 text-red-500 border-red-500/20';
45+
default:
46+
return 'bg-gray-500/10 text-gray-500 border-gray-500/20';
47+
}
48+
};
49+
50+
const getSessionTypeLabel = (sessionType: string) => {
51+
const labels: Record<string, string> = {
52+
'bug-review': 'Bug Review',
53+
'bug-resolution-plan': 'Resolution Plan',
54+
'bug-implement-fix': 'Fix Implementation',
55+
'generic': 'Generic',
56+
};
57+
return labels[sessionType] || sessionType;
58+
};
59+
60+
export function BugFixSessionsTable({
61+
sessions,
62+
projectName,
63+
workflowId,
64+
workflow,
65+
sessionsLoading,
66+
}: BugFixSessionsTableProps) {
67+
const router = useRouter();
68+
69+
return (
70+
<Card>
71+
<CardHeader>
72+
<div className="flex items-start justify-between gap-4">
73+
<div className="flex-1">
74+
<CardTitle>Sessions ({sessions?.length || 0})</CardTitle>
75+
<CardDescription>Agentic sessions for this bug fix workspace</CardDescription>
76+
</div>
77+
<SessionSelector
78+
projectName={projectName}
79+
workflowId={workflowId}
80+
githubIssueNumber={workflow.githubIssueNumber}
81+
disabled={workflow.phase !== 'Ready'}
82+
/>
83+
</div>
84+
{workflow.phase !== 'Ready' && (
85+
<p className="text-xs text-muted-foreground mt-2">
86+
Workspace must be in &quot;Ready&quot; state to create sessions
87+
</p>
88+
)}
89+
</CardHeader>
90+
<CardContent>
91+
{sessionsLoading ? (
92+
<div className="flex items-center justify-center py-8">
93+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
94+
</div>
95+
) : sessions && sessions.length === 0 ? (
96+
<div className="text-center py-8 text-muted-foreground">
97+
No sessions created yet
98+
</div>
99+
) : (
100+
<div className="overflow-x-auto">
101+
<Table>
102+
<TableHeader>
103+
<TableRow>
104+
<TableHead className="min-w-[220px]">Title</TableHead>
105+
<TableHead>Type</TableHead>
106+
<TableHead>Status</TableHead>
107+
<TableHead className="hidden lg:table-cell">Created</TableHead>
108+
</TableRow>
109+
</TableHeader>
110+
<TableBody>
111+
{sessions.map((session) => (
112+
<TableRow
113+
key={session.id}
114+
className="cursor-pointer hover:bg-muted/50"
115+
onClick={() => router.push(`/projects/${projectName}/sessions/${session.id}`)}
116+
>
117+
<TableCell className="font-medium">{session.title}</TableCell>
118+
<TableCell>
119+
<Badge variant="outline">{getSessionTypeLabel(session.sessionType)}</Badge>
120+
</TableCell>
121+
<TableCell>
122+
<Badge variant="outline" className={getPhaseColor(session.phase)}>
123+
{session.phase}
124+
</Badge>
125+
</TableCell>
126+
<TableCell className="hidden lg:table-cell text-sm text-muted-foreground">
127+
{formatDistanceToNow(new Date(session.createdAt), { addSuffix: true })}
128+
</TableCell>
129+
</TableRow>
130+
))}
131+
</TableBody>
132+
</Table>
133+
</div>
134+
)}
135+
</CardContent>
136+
</Card>
137+
);
138+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
'use client';
2+
3+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
4+
import { Badge } from '@/components/ui/badge';
5+
import { formatDistanceToNow } from 'date-fns';
6+
import { Button } from '@/components/ui/button';
7+
import { RefreshCw } from 'lucide-react';
8+
import { bugfixApi } from '@/services/api';
9+
import { useMutation, useQueryClient } from '@tanstack/react-query';
10+
import { successToast, errorToast } from '@/hooks/use-toast';
11+
12+
type JiraIntegrationCardProps = {
13+
projectName: string;
14+
workflowId: string;
15+
githubIssueNumber: number;
16+
jiraTaskKey?: string;
17+
jiraTaskURL?: string;
18+
lastSyncedAt?: string;
19+
};
20+
21+
export function JiraIntegrationCard({
22+
projectName,
23+
workflowId,
24+
githubIssueNumber, // eslint-disable-line @typescript-eslint/no-unused-vars
25+
jiraTaskKey,
26+
jiraTaskURL,
27+
lastSyncedAt,
28+
}: JiraIntegrationCardProps) {
29+
const queryClient = useQueryClient();
30+
31+
const syncMutation = useMutation({
32+
mutationFn: () => bugfixApi.syncBugFixToJira(projectName, workflowId),
33+
onSuccess: (result) => {
34+
const message = result.created
35+
? `Created Jira task ${result.jiraTaskKey}`
36+
: `Updated Jira task ${result.jiraTaskKey}`;
37+
successToast(message);
38+
39+
// Invalidate workflow query to refresh UI
40+
queryClient.invalidateQueries({ queryKey: ['bugfix-workflow', projectName, workflowId] });
41+
},
42+
onError: (error: Error) => {
43+
errorToast(error.message || 'Failed to sync with Jira');
44+
},
45+
});
46+
47+
const handleSync = () => {
48+
syncMutation.mutate();
49+
};
50+
51+
const isSynced = Boolean(jiraTaskKey && jiraTaskURL);
52+
53+
return (
54+
<Card>
55+
<CardHeader>
56+
<CardTitle>Jira Integration</CardTitle>
57+
<CardDescription>Sync this bug fix workspace to Jira for project management tracking</CardDescription>
58+
</CardHeader>
59+
<CardContent>
60+
<div className="space-y-4">
61+
{/* Status Section */}
62+
<div className="space-y-2">
63+
<div className="text-sm font-medium">Status</div>
64+
{isSynced ? (
65+
<>
66+
<div className="flex items-center gap-2">
67+
<Badge variant="outline">{jiraTaskKey}</Badge>
68+
<Button
69+
variant="link"
70+
size="sm"
71+
className="px-0 h-auto"
72+
onClick={() => jiraTaskURL && window.open(jiraTaskURL, '_blank')}
73+
>
74+
Open in Jira
75+
</Button>
76+
</div>
77+
{lastSyncedAt && (
78+
<div className="text-xs text-muted-foreground">
79+
Last synced {formatDistanceToNow(new Date(lastSyncedAt), { addSuffix: true })}
80+
</div>
81+
)}
82+
</>
83+
) : (
84+
<span className="text-sm text-muted-foreground">Not synced to Jira yet</span>
85+
)}
86+
</div>
87+
88+
{/* Actions Section */}
89+
<div className="pt-2 border-t">
90+
<Button
91+
onClick={handleSync}
92+
disabled={syncMutation.isPending}
93+
variant={isSynced ? 'outline' : 'default'}
94+
size="default"
95+
>
96+
<RefreshCw className={`mr-2 h-4 w-4 ${syncMutation.isPending ? 'animate-spin' : ''}`} />
97+
{syncMutation.isPending
98+
? 'Syncing...'
99+
: isSynced
100+
? 'Update Jira'
101+
: 'Sync to Jira'}
102+
</Button>
103+
</div>
104+
</div>
105+
</CardContent>
106+
</Card>
107+
);
108+
}

0 commit comments

Comments
 (0)