mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
feat: add tests and implementation for issue discovery and queue pages
- Implemented `DiscoveryPage` with session management and findings display. - Added tests for `DiscoveryPage` to ensure proper rendering and functionality. - Created `QueuePage` for managing issue execution queues with stats and actions. - Added tests for `QueuePage` to verify UI elements and translations. - Introduced `useIssues` hooks for fetching and managing issue data. - Added loading skeletons and error handling for better user experience. - Created `vite-env.d.ts` for TypeScript support in Vite environment.
This commit is contained in:
174
ccw/frontend/src/pages/DiscoveryPage.tsx
Normal file
174
ccw/frontend/src/pages/DiscoveryPage.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
// ========================================
|
||||
// Issue Discovery Page
|
||||
// ========================================
|
||||
// Track discovery sessions and view findings from multiple perspectives
|
||||
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Radar, AlertCircle, Loader2 } from 'lucide-react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { useIssueDiscovery } from '@/hooks/useIssues';
|
||||
import { DiscoveryCard } from '@/components/issue/discovery/DiscoveryCard';
|
||||
import { DiscoveryDetail } from '@/components/issue/discovery/DiscoveryDetail';
|
||||
|
||||
export function DiscoveryPage() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const {
|
||||
sessions,
|
||||
activeSession,
|
||||
findings,
|
||||
isLoadingSessions,
|
||||
isLoadingFindings,
|
||||
error,
|
||||
filters,
|
||||
setFilters,
|
||||
selectSession,
|
||||
exportFindings,
|
||||
} = useIssueDiscovery({ refetchInterval: 3000 });
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Radar className="w-6 h-6 text-primary" />
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{formatMessage({ id: 'issues.discovery.title' })}
|
||||
</h1>
|
||||
</div>
|
||||
<Card className="p-8 text-center">
|
||||
<AlertCircle className="w-12 h-12 mx-auto text-destructive" />
|
||||
<h3 className="mt-4 text-lg font-medium text-foreground">
|
||||
{formatMessage({ id: 'common.error' })}
|
||||
</h3>
|
||||
<p className="mt-2 text-muted-foreground">{error.message}</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Page Header */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Radar className="w-6 h-6 text-primary" />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{formatMessage({ id: 'issues.discovery.title' })}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm mt-1">
|
||||
{formatMessage({ id: 'issues.discovery.description' })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Radar className="w-5 h-5 text-primary" />
|
||||
<span className="text-2xl font-bold">{sessions.length}</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{formatMessage({ id: 'issues.discovery.totalSessions' })}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="success" className="w-5 h-5 flex items-center justify-center p-0">
|
||||
{sessions.filter(s => s.status === 'completed').length}
|
||||
</Badge>
|
||||
<span className="text-2xl font-bold">{sessions.filter(s => s.status === 'completed').length}</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{formatMessage({ id: 'issues.discovery.completedSessions' })}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="warning" className="w-5 h-5 flex items-center justify-center p-0">
|
||||
{sessions.filter(s => s.status === 'running').length}
|
||||
</Badge>
|
||||
<span className="text-2xl font-bold">{sessions.filter(s => s.status === 'running').length}</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{formatMessage({ id: 'issues.discovery.runningSessions' })}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-2xl font-bold">
|
||||
{sessions.reduce((sum, s) => sum + s.findings_count, 0)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{formatMessage({ id: 'issues.discovery.totalFindings' })}
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Main Content: Split Pane */}
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{/* Left: Session List */}
|
||||
<div className="md:col-span-1 space-y-4">
|
||||
<h2 className="text-lg font-medium text-foreground">
|
||||
{formatMessage({ id: 'issues.discovery.sessionList' })}
|
||||
</h2>
|
||||
|
||||
{isLoadingSessions ? (
|
||||
<div className="space-y-3">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="h-32 bg-muted animate-pulse rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
) : sessions.length === 0 ? (
|
||||
<Card className="p-8 text-center">
|
||||
<Radar className="w-12 h-12 mx-auto text-muted-foreground/50" />
|
||||
<h3 className="mt-4 text-lg font-medium text-foreground">
|
||||
{formatMessage({ id: 'issues.discovery.noSessions' })}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'issues.discovery.noSessionsDescription' })}
|
||||
</p>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{sessions.map((session) => (
|
||||
<DiscoveryCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
isActive={activeSession?.id === session.id}
|
||||
onClick={() => selectSession(session.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right: Findings Detail */}
|
||||
<div className="md:col-span-2 space-y-4">
|
||||
<h2 className="text-lg font-medium text-foreground">
|
||||
{formatMessage({ id: 'issues.discovery.findingsDetail' })}
|
||||
</h2>
|
||||
|
||||
{isLoadingFindings ? (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<DiscoveryDetail
|
||||
sessionId={activeSession?.id || ''}
|
||||
session={activeSession}
|
||||
findings={findings}
|
||||
filters={filters}
|
||||
onFilterChange={setFilters}
|
||||
onExport={exportFindings}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DiscoveryPage;
|
||||
Reference in New Issue
Block a user