Add categorized issue display for revision analysis
When analyzing proof revisions (version > 1), SubReviewCard now displays issues in three distinct categories: Resolved Issues (green, collapsed by default), Outstanding Issues (amber), and New Issues (red). Each section is collapsible with count badges. Original mode (version 1) maintains backward compatibility with the single "Actionable Issues" list. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bf1d689f42
commit
e5a841716e
1 changed files with 244 additions and 7 deletions
|
|
@ -241,9 +241,18 @@ const FlagIssueModal: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
const SubReviewCard: React.FC<{
|
||||
title: string;
|
||||
review: SubReview;
|
||||
// Helper function to detect revision mode (when revision data is present)
|
||||
const hasRevisionData = (review: SubReview): boolean => {
|
||||
return (
|
||||
(review.resolvedIssues && review.resolvedIssues.length > 0) ||
|
||||
(review.outstandingIssues && review.outstandingIssues.length > 0) ||
|
||||
(review.newIssues && review.newIssues.length > 0)
|
||||
);
|
||||
};
|
||||
|
||||
const SubReviewCard: React.FC<{
|
||||
title: string;
|
||||
review: SubReview;
|
||||
onFlag: () => void;
|
||||
onResolve: (issueText: string, reason: string) => void;
|
||||
}> = ({ title, review, onFlag, onResolve }) => {
|
||||
|
|
@ -251,15 +260,39 @@ const SubReviewCard: React.FC<{
|
|||
text: string;
|
||||
status: 'actionable' | 'resolved';
|
||||
reason?: string;
|
||||
category?: 'outstanding' | 'new'; // For revision mode
|
||||
}
|
||||
|
||||
const [issues, setIssues] = useState<IssueState[]>([]);
|
||||
const [currentStatus, setCurrentStatus] = useState<RagStatus>(review.ragStatus);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [activeIssueIndex, setActiveIssueIndex] = useState<number | null>(null);
|
||||
|
||||
|
||||
// Section collapse states for revision mode
|
||||
const [resolvedCollapsed, setResolvedCollapsed] = useState(true);
|
||||
const [outstandingCollapsed, setOutstandingCollapsed] = useState(false);
|
||||
const [newCollapsed, setNewCollapsed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIssues(review.issues.map(text => ({ text, status: 'actionable', reason: undefined })));
|
||||
if (hasRevisionData(review)) {
|
||||
// Revision mode: populate from outstandingIssues and newIssues
|
||||
const outstandingIssues: IssueState[] = (review.outstandingIssues || []).map(text => ({
|
||||
text,
|
||||
status: 'actionable',
|
||||
reason: undefined,
|
||||
category: 'outstanding' as const
|
||||
}));
|
||||
const newIssues: IssueState[] = (review.newIssues || []).map(text => ({
|
||||
text,
|
||||
status: 'actionable',
|
||||
reason: undefined,
|
||||
category: 'new' as const
|
||||
}));
|
||||
setIssues([...outstandingIssues, ...newIssues]);
|
||||
} else {
|
||||
// Original mode: use review.issues
|
||||
setIssues(review.issues.map(text => ({ text, status: 'actionable', reason: undefined })));
|
||||
}
|
||||
setCurrentStatus(review.ragStatus);
|
||||
}, [review]);
|
||||
|
||||
|
|
@ -371,7 +404,211 @@ const SubReviewCard: React.FC<{
|
|||
{formatFeedbackText(review.feedback)}
|
||||
</div>
|
||||
|
||||
{currentStatus !== 'Error' && issues.length > 0 && (
|
||||
{/* Revision mode: Show categorized sections */}
|
||||
{currentStatus !== 'Error' && hasRevisionData(review) && (
|
||||
<div className="space-y-4">
|
||||
{/* Resolved Issues Section */}
|
||||
{review.resolvedIssues && review.resolvedIssues.length > 0 && (
|
||||
<div className="bg-emerald-50/50 rounded-xl border border-emerald-100 overflow-hidden">
|
||||
<button
|
||||
onClick={() => setResolvedCollapsed(!resolvedCollapsed)}
|
||||
className="w-full flex items-center justify-between p-4 hover:bg-emerald-50/80 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircleIcon className="h-5 w-5 text-emerald-500" />
|
||||
<h5 className="text-xs font-bold uppercase tracking-wider text-emerald-600">
|
||||
Resolved Issues
|
||||
</h5>
|
||||
<span className="bg-emerald-100 text-emerald-700 text-xs font-semibold px-2 py-0.5 rounded-full">
|
||||
{review.resolvedIssues.length}
|
||||
</span>
|
||||
</div>
|
||||
<svg
|
||||
className={`h-4 w-4 text-emerald-500 transition-transform ${resolvedCollapsed ? '' : 'rotate-180'}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
{!resolvedCollapsed && (
|
||||
<ul className="px-4 pb-4 space-y-2">
|
||||
{review.resolvedIssues.map((issueText, index) => (
|
||||
<li key={`resolved-${index}`} className="flex items-start gap-3">
|
||||
<CheckCircleIcon className="h-5 w-5 text-emerald-500 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-sm text-emerald-700 line-through opacity-75">
|
||||
{issueText}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Outstanding Issues Section */}
|
||||
{issues.filter(i => i.category === 'outstanding').length > 0 && (
|
||||
<div className="bg-amber-50/50 rounded-xl border border-amber-100 overflow-hidden">
|
||||
<button
|
||||
onClick={() => setOutstandingCollapsed(!outstandingCollapsed)}
|
||||
className="w-full flex items-center justify-between p-4 hover:bg-amber-50/80 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-amber-500" />
|
||||
<h5 className="text-xs font-bold uppercase tracking-wider text-amber-600">
|
||||
Outstanding Issues
|
||||
</h5>
|
||||
<span className="bg-amber-100 text-amber-700 text-xs font-semibold px-2 py-0.5 rounded-full">
|
||||
{issues.filter(i => i.category === 'outstanding' && i.status === 'actionable').length}
|
||||
</span>
|
||||
</div>
|
||||
<svg
|
||||
className={`h-4 w-4 text-amber-500 transition-transform ${outstandingCollapsed ? '' : 'rotate-180'}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
{!outstandingCollapsed && (
|
||||
<ul className="px-4 pb-4 space-y-3">
|
||||
{issues.filter(i => i.category === 'outstanding').map((issue, idx) => {
|
||||
const actualIndex = issues.findIndex((i, index) => i === issue && index >= 0);
|
||||
return (
|
||||
<li key={`outstanding-${idx}`} className="flex items-start gap-3 group/issue">
|
||||
{issue.status === 'actionable' ? (
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-amber-500 mt-0.5 flex-shrink-0" />
|
||||
) : (
|
||||
<CheckCircleIcon className="h-5 w-5 text-emerald-500 mt-0.5 flex-shrink-0" />
|
||||
)}
|
||||
<div className="flex-grow pt-0.5">
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-2">
|
||||
<span className={`text-sm ${issue.status === 'resolved' ? 'line-through text-slate-400' : 'text-amber-800'}`}>
|
||||
{issue.text}
|
||||
</span>
|
||||
{issue.status === 'actionable' ? (
|
||||
<button
|
||||
onClick={() => handleOpenModal(actualIndex)}
|
||||
className="flex-shrink-0 opacity-0 group-hover/issue:opacity-100 focus:opacity-100 text-xs font-semibold text-brand-accent hover:text-brand-dark-blue bg-brand-light-blue/10 hover:bg-brand-light-blue/20 px-2.5 py-1 rounded-lg transition-all"
|
||||
>
|
||||
Mark Resolved
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="relative inline-block group/tooltip">
|
||||
<InformationCircleIcon className="h-4 w-4 text-slate-300 cursor-help" />
|
||||
<div className="absolute bottom-full right-0 mb-2 w-64 bg-slate-800 text-white text-xs rounded-lg py-2 px-3 opacity-0 group-hover/tooltip:opacity-100 transition-opacity pointer-events-none z-20 shadow-xl">
|
||||
<span className="font-bold block mb-1">Reason for resolution:</span>
|
||||
"{issue.reason}"
|
||||
</div>
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleReopen(actualIndex)}
|
||||
className="text-xs font-semibold text-slate-400 hover:text-brand-dark-blue bg-slate-100 hover:bg-slate-200 px-2.5 py-1 rounded-lg transition-all"
|
||||
>
|
||||
Re-open
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* New Issues Section */}
|
||||
{issues.filter(i => i.category === 'new').length > 0 && (
|
||||
<div className="bg-red-50/50 rounded-xl border border-red-100 overflow-hidden">
|
||||
<button
|
||||
onClick={() => setNewCollapsed(!newCollapsed)}
|
||||
className="w-full flex items-center justify-between p-4 hover:bg-red-50/80 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-red-500" />
|
||||
<h5 className="text-xs font-bold uppercase tracking-wider text-red-600">
|
||||
New Issues
|
||||
</h5>
|
||||
<span className="bg-red-100 text-red-700 text-xs font-semibold px-2 py-0.5 rounded-full">
|
||||
{issues.filter(i => i.category === 'new' && i.status === 'actionable').length}
|
||||
</span>
|
||||
</div>
|
||||
<svg
|
||||
className={`h-4 w-4 text-red-500 transition-transform ${newCollapsed ? '' : 'rotate-180'}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
{!newCollapsed && (
|
||||
<ul className="px-4 pb-4 space-y-3">
|
||||
{issues.filter(i => i.category === 'new').map((issue, idx) => {
|
||||
const actualIndex = issues.findIndex((i, index) => i === issue && index >= 0);
|
||||
return (
|
||||
<li key={`new-${idx}`} className="flex items-start gap-3 group/issue">
|
||||
{issue.status === 'actionable' ? (
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-red-500 mt-0.5 flex-shrink-0" />
|
||||
) : (
|
||||
<CheckCircleIcon className="h-5 w-5 text-emerald-500 mt-0.5 flex-shrink-0" />
|
||||
)}
|
||||
<div className="flex-grow pt-0.5">
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-2">
|
||||
<span className={`text-sm ${issue.status === 'resolved' ? 'line-through text-slate-400' : 'text-red-800'}`}>
|
||||
{issue.text}
|
||||
</span>
|
||||
{issue.status === 'actionable' ? (
|
||||
<button
|
||||
onClick={() => handleOpenModal(actualIndex)}
|
||||
className="flex-shrink-0 opacity-0 group-hover/issue:opacity-100 focus:opacity-100 text-xs font-semibold text-brand-accent hover:text-brand-dark-blue bg-brand-light-blue/10 hover:bg-brand-light-blue/20 px-2.5 py-1 rounded-lg transition-all"
|
||||
>
|
||||
Mark Resolved
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="relative inline-block group/tooltip">
|
||||
<InformationCircleIcon className="h-4 w-4 text-slate-300 cursor-help" />
|
||||
<div className="absolute bottom-full right-0 mb-2 w-64 bg-slate-800 text-white text-xs rounded-lg py-2 px-3 opacity-0 group-hover/tooltip:opacity-100 transition-opacity pointer-events-none z-20 shadow-xl">
|
||||
<span className="font-bold block mb-1">Reason for resolution:</span>
|
||||
"{issue.reason}"
|
||||
</div>
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleReopen(actualIndex)}
|
||||
className="text-xs font-semibold text-slate-400 hover:text-brand-dark-blue bg-slate-100 hover:bg-slate-200 px-2.5 py-1 rounded-lg transition-all"
|
||||
>
|
||||
Re-open
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* No issues state for revision mode */}
|
||||
{issues.length === 0 && (!review.resolvedIssues || review.resolvedIssues.length === 0) && (
|
||||
<div className="flex items-center p-3 rounded-xl bg-emerald-50/50 border border-emerald-100 text-emerald-700 text-sm">
|
||||
<CheckCircleIcon className="h-5 w-5 mr-2" />
|
||||
<span className="font-semibold">No actionable issues found.</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Original mode: Show single list (backward compatible) */}
|
||||
{currentStatus !== 'Error' && !hasRevisionData(review) && issues.length > 0 && (
|
||||
<div className="bg-white/50 rounded-xl border border-slate-100 p-4">
|
||||
<h5 className={`text-xs font-bold uppercase tracking-wider mb-3 ${issueIconColor}`}>Actionable Issues</h5>
|
||||
<ul className="space-y-3">
|
||||
|
|
@ -418,7 +655,7 @@ const SubReviewCard: React.FC<{
|
|||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{currentStatus !== 'Error' && issues.length === 0 && (
|
||||
{currentStatus !== 'Error' && !hasRevisionData(review) && issues.length === 0 && (
|
||||
<div className="flex items-center p-3 rounded-xl bg-emerald-50/50 border border-emerald-100 text-emerald-700 text-sm">
|
||||
<CheckCircleIcon className="h-5 w-5 mr-2" />
|
||||
<span className="font-semibold">No actionable issues found.</span>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue