/**
* DevQALoop - Developer ↔ QA Review Loop Interface
*
* Visualizes the iterative review cycle between Developer and QA agents,
* showing iteration count, feedback history, and current state.
*
* @module components/DevQALoop
*/
import { useState } from 'react';
import { useTeamStore } from '../store/teamStore';
import type { DevQALoop as DevQALoopType, ReviewFeedback, ReviewIssue } from '../types/team';
import {
RefreshCw, CheckCircle, XCircle, AlertTriangle,
Clock, MessageSquare, FileCode, Bug, Lightbulb, ChevronDown, ChevronUp,
Send, ThumbsUp, ThumbsDown, AlertOctagon,
} from 'lucide-react';
// === Sub-Components ===
interface FeedbackItemProps {
feedback: ReviewFeedback;
iteration: number;
isExpanded: boolean;
onToggle: () => void;
}
function FeedbackItem({ feedback, iteration, isExpanded, onToggle }: FeedbackItemProps) {
const verdictColors = {
approved: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300',
needs_work: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300',
rejected: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300',
};
const verdictIcons = {
approved: ,
needs_work: ,
rejected: ,
};
const severityColors = {
critical: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300',
major: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300',
minor: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300',
suggestion: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
};
return (
{isExpanded && (
{/* Comments */}
{feedback.comments.length > 0 && (
Comments
{feedback.comments.map((comment, idx) => (
-
{comment}
))}
)}
{/* Issues */}
{feedback.issues.length > 0 && (
Issues ({feedback.issues.length})
{feedback.issues.map((issue, idx) => (
{issue.severity}
{issue.file && (
{issue.file}{issue.line ? `:${issue.line}` : ''}
)}
{issue.description}
{issue.suggestion && (
{issue.suggestion}
)}
))}
)}
)}
);
}
interface ReviewFormProps {
loopId: string;
teamId: string;
onSubmit: (feedback: Omit) => void;
onCancel: () => void;
}
function ReviewForm({ loopId: _loopId, teamId: _teamId, onSubmit, onCancel }: ReviewFormProps) {
const [verdict, setVerdict] = useState('needs_work');
const [comment, setComment] = useState('');
const [issues, setIssues] = useState([]);
const [newIssue, setNewIssue] = useState>({});
const handleAddIssue = () => {
if (!newIssue.description) return;
setIssues([...issues, {
severity: newIssue.severity || 'minor',
description: newIssue.description,
file: newIssue.file,
line: newIssue.line,
suggestion: newIssue.suggestion,
} as ReviewIssue]);
setNewIssue({});
};
const handleSubmit = () => {
onSubmit({
verdict,
comments: comment ? [comment] : [],
issues,
});
};
return (
Submit Review
{/* Verdict */}
{(['approved', 'needs_work', 'rejected'] as const).map(v => (
))}
{/* Comment */}
{/* Add Issue */}
{/* Issues List */}
{issues.length > 0 && (
{issues.map((issue, idx) => (
{issue.severity}
{issue.description}
))}
)}
{/* Actions */}
);
}
// === Main Component ===
interface DevQALoopPanelProps {
loop: DevQALoopType;
teamId: string;
developerName: string;
reviewerName: string;
taskTitle: string;
}
export function DevQALoopPanel({ loop, teamId, developerName, reviewerName, taskTitle }: DevQALoopPanelProps) {
const [expandedFeedback, setExpandedFeedback] = useState(null);
const [showReviewForm, setShowReviewForm] = useState(false);
const { submitReview, updateLoopState } = useTeamStore();
const stateConfig = {
idle: { color: 'gray', icon: , label: 'Idle' },
developing: { color: 'blue', icon: , label: 'Developing' },
reviewing: { color: 'yellow', icon: , label: 'Reviewing' },
revising: { color: 'orange', icon: , label: 'Revising' },
approved: { color: 'green', icon: , label: 'Approved' },
escalated: { color: 'red', icon: , label: 'Escalated' },
};
const config = stateConfig[loop.state];
const handleSubmitReview = async (feedback: Omit) => {
await submitReview(teamId, loop.id, feedback);
setShowReviewForm(false);
};
const handleCompleteRevision = async () => {
await updateLoopState(teamId, loop.id, 'reviewing');
};
return (
{/* Header */}
{config.label}
Iteration {loop.iterationCount + 1}/{loop.maxIterations}
{/* Flow Visualization */}
{developerName}
Developer
{reviewerName}
QA Reviewer
{/* Progress Bar */}
{Array.from({ length: loop.maxIterations }).map((_, i) => (
))}
{/* Feedback History */}
Feedback History
{loop.feedbackHistory.length === 0 ? (
No feedback yet. First review pending.
) : (
loop.feedbackHistory.map((feedback, idx) => (
setExpandedFeedback(expandedFeedback === idx ? null : idx)}
/>
))
)}
{/* Actions */}
{loop.state === 'escalated' ? (
Maximum iterations reached. Human intervention required.
) : loop.state === 'approved' ? (
Task approved and completed!
) : loop.state === 'reviewing' && !showReviewForm ? (
) : loop.state === 'revising' ? (
) : showReviewForm ? (
setShowReviewForm(false)}
/>
) : null}
);
}
export default DevQALoopPanel;