Fix flagging feature: blue link in Auditing, remove alert popup, show solid red flag icon
- Style Proof Name column in Auditing Flags tab as blue clickable link - Replace browser alert() with in-app success message in flag modal that auto-closes after 2s - Add filled prop to FlagIcon for solid red variant when flagged - Thread flaggedItems from App → Campaigns → ProofDetailView → FeedbackReport - Show solid red flag icon on SubReviewCard and LeadAgentSummary when agent has been flagged Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
33c2ce5cf4
commit
9ec892b46b
5 changed files with 93 additions and 58 deletions
|
|
@ -817,6 +817,7 @@ const App: React.FC = () => {
|
|||
onDeleteCampaign={handleDeleteCampaign}
|
||||
onFlagSubmit={handleFlagSubmit}
|
||||
onResolveSubmit={handleResolveSubmit}
|
||||
flaggedItems={flaggedItems}
|
||||
/>;
|
||||
case 'WIP Reviewer':
|
||||
return <WIPReviewer dropdownOptions={dropdownOptions} msalInstance={msalInstance} />;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const FlagsTable: React.FC<{ items: FlaggedItem[], onNavigate: AuditingProps['on
|
|||
onClick={() => onNavigate(item)}
|
||||
title={`Click to view Version ${item.version} of ${item.proofName}`}
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-black-title">{item.proofName}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-semibold text-active-blue">{item.proofName}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-black-title">Version {item.version}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-black-title">{item.submitter}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-black-title">{item.submitAgency}</td>
|
||||
|
|
|
|||
|
|
@ -1268,7 +1268,8 @@ const ProofDetailView: React.FC<{
|
|||
isUploadingNewVersion: boolean;
|
||||
onFlagSubmit: (flagData: Omit<FlaggedItem, 'id' | 'timestamp' | 'submitter' | 'submitAgency'>) => void;
|
||||
onResolveSubmit: (resolveData: Omit<ResolvedItem, 'id' | 'timestamp' | 'submitter' | 'submitAgency'>) => void;
|
||||
}> = ({ campaignName, proof, onBack, onNewVersionUpload, isUploadingNewVersion, onFlagSubmit, onResolveSubmit }) => {
|
||||
flaggedItems: FlaggedItem[];
|
||||
}> = ({ campaignName, proof, onBack, onNewVersionUpload, isUploadingNewVersion, onFlagSubmit, onResolveSubmit, flaggedItems }) => {
|
||||
|
||||
const getInitialVersionIndex = () => {
|
||||
if (proof.initialVersion && proof.versions) {
|
||||
|
|
@ -1596,6 +1597,9 @@ const ProofDetailView: React.FC<{
|
|||
feedback={selectedVersion.feedback}
|
||||
onFlagSubmit={handleFlagSubmitWrapper}
|
||||
onResolveSubmit={handleResolveSubmitWrapper}
|
||||
flaggedItems={flaggedItems}
|
||||
proofName={proof.proofName}
|
||||
version={selectedVersion.version}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1622,6 +1626,7 @@ interface CampaignsProps {
|
|||
onDeleteCampaign: (campaignName: string) => Promise<void>;
|
||||
onFlagSubmit: (flagData: Omit<FlaggedItem, 'id' | 'timestamp' | 'submitter' | 'submitAgency'>) => void;
|
||||
onResolveSubmit: (resolveData: Omit<ResolvedItem, 'id' | 'timestamp' | 'submitter' | 'submitAgency'>) => void;
|
||||
flaggedItems: FlaggedItem[];
|
||||
}
|
||||
|
||||
export const Campaigns: React.FC<CampaignsProps> = ({
|
||||
|
|
@ -1642,6 +1647,7 @@ export const Campaigns: React.FC<CampaignsProps> = ({
|
|||
onDeleteCampaign,
|
||||
onFlagSubmit,
|
||||
onResolveSubmit,
|
||||
flaggedItems,
|
||||
}) => {
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
|
@ -1670,6 +1676,7 @@ export const Campaigns: React.FC<CampaignsProps> = ({
|
|||
isUploadingNewVersion={isUploadingNewVersion}
|
||||
onFlagSubmit={onFlagSubmit}
|
||||
onResolveSubmit={onResolveSubmit}
|
||||
flaggedItems={flaggedItems}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import type { AgentReview, SubReview, RagStatus, OverallStatus } from '../types';
|
||||
import type { AgentReview, SubReview, RagStatus, OverallStatus, FlaggedItem } from '../types';
|
||||
import { CheckCircleIcon, ExclamationTriangleIcon, InformationCircleIcon } from './icons/StatusIcons';
|
||||
import { FlagIcon } from './icons/FlagIcon';
|
||||
import { XIcon } from './icons/XIcon';
|
||||
|
|
@ -185,57 +185,74 @@ const FlagIssueModal: React.FC<{
|
|||
if (!isOpen) return null;
|
||||
|
||||
const [comments, setComments] = useState('');
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onSubmit(comments);
|
||||
setShowSuccess(true);
|
||||
setTimeout(() => {
|
||||
setShowSuccess(false);
|
||||
setComments('');
|
||||
onClose();
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm flex items-center justify-center z-50 transition-opacity duration-300"
|
||||
onClick={onClose}
|
||||
onClick={showSuccess ? undefined : onClose}
|
||||
>
|
||||
<div
|
||||
className="bg-white rounded-2xl shadow-2xl p-8 w-full max-w-lg transform transition-all border border-white/20 ring-1 ring-black/5"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-primary-blue">Flag Feedback</h3>
|
||||
<p className="text-slate-500 text-sm mt-1">Reporting incorrect feedback from <span className="font-semibold text-active-blue">{agentName}</span></p>
|
||||
{showSuccess ? (
|
||||
<div className="text-center py-8">
|
||||
<CheckCircleIcon className="h-12 w-12 text-success mx-auto mb-4" />
|
||||
<h3 className="text-xl font-bold text-primary-blue mb-2">Flag Submitted</h3>
|
||||
<p className="text-slate-500">Thank you for your feedback on the {agentName}'s review. This has been logged for auditing.</p>
|
||||
</div>
|
||||
<button onClick={onClose} className="p-2 rounded-full hover:bg-slate-100 text-slate-400 hover:text-slate-600 transition-colors">
|
||||
<XIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-primary-blue">Flag Feedback</h3>
|
||||
<p className="text-slate-500 text-sm mt-1">Reporting incorrect feedback from <span className="font-semibold text-active-blue">{agentName}</span></p>
|
||||
</div>
|
||||
<button onClick={onClose} className="p-2 rounded-full hover:bg-slate-100 text-slate-400 hover:text-slate-600 transition-colors">
|
||||
<XIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label htmlFor="flag-comments" className="block text-sm font-bold text-slate-700 mb-2">Additional Comments</label>
|
||||
<textarea
|
||||
id="flag-comments"
|
||||
value={comments}
|
||||
onChange={(e) => setComments(e.target.value)}
|
||||
className="w-full p-4 border border-slate-200 rounded-xl focus:ring-2 focus:ring-red-500/30 focus:border-red-500 transition-all bg-slate-50 focus:bg-white resize-none"
|
||||
rows={5}
|
||||
placeholder="Please explain why this feedback is incorrect..."
|
||||
/>
|
||||
<div className="mt-8 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-5 py-2.5 rounded-xl text-slate-600 font-semibold hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-5 py-2.5 rounded-xl bg-red-600 text-white font-bold shadow-lg shadow-red-600/30 hover:bg-red-500 transition-all"
|
||||
>
|
||||
Submit Flag
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label htmlFor="flag-comments" className="block text-sm font-bold text-slate-700 mb-2">Additional Comments</label>
|
||||
<textarea
|
||||
id="flag-comments"
|
||||
value={comments}
|
||||
onChange={(e) => setComments(e.target.value)}
|
||||
className="w-full p-4 border border-slate-200 rounded-xl focus:ring-2 focus:ring-red-500/30 focus:border-red-500 transition-all bg-slate-50 focus:bg-white resize-none"
|
||||
rows={5}
|
||||
placeholder="Please explain why this feedback is incorrect..."
|
||||
/>
|
||||
<div className="mt-8 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-5 py-2.5 rounded-xl text-slate-600 font-semibold hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-5 py-2.5 rounded-xl bg-red-600 text-white font-bold shadow-lg shadow-red-600/30 hover:bg-red-500 transition-all"
|
||||
>
|
||||
Submit Flag
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -255,7 +272,8 @@ const SubReviewCard: React.FC<{
|
|||
review: SubReview;
|
||||
onFlag: () => void;
|
||||
onResolve: (issueText: string, reason: string) => void;
|
||||
}> = ({ title, review, onFlag, onResolve }) => {
|
||||
isFlagged?: boolean;
|
||||
}> = ({ title, review, onFlag, onResolve, isFlagged }) => {
|
||||
interface IssueState {
|
||||
text: string;
|
||||
status: 'actionable' | 'resolved';
|
||||
|
|
@ -391,11 +409,11 @@ const SubReviewCard: React.FC<{
|
|||
<RagStatusBadge status={currentStatus} />
|
||||
<button
|
||||
onClick={onFlag}
|
||||
className="p-1.5 text-slate-400 rounded-lg hover:bg-red-50 hover:text-red-500 transition-colors"
|
||||
title="Flag as incorrect"
|
||||
aria-label="Flag this feedback as incorrect"
|
||||
className={`p-1.5 rounded-lg transition-colors ${isFlagged ? 'text-red-500' : 'text-slate-400 hover:bg-red-50 hover:text-red-500'}`}
|
||||
title={isFlagged ? "Flagged as incorrect" : "Flag as incorrect"}
|
||||
aria-label={isFlagged ? "This feedback has been flagged" : "Flag this feedback as incorrect"}
|
||||
>
|
||||
<FlagIcon className="h-4 w-4" />
|
||||
<FlagIcon className="h-4 w-4" filled={isFlagged} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -667,7 +685,7 @@ const SubReviewCard: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
const LeadAgentSummary: React.FC<{ status: OverallStatus, summary: string, onFlag: () => void; }> = ({ status, summary, onFlag }) => {
|
||||
const LeadAgentSummary: React.FC<{ status: OverallStatus, summary: string, onFlag: () => void; isFlagged?: boolean; }> = ({ status, summary, onFlag, isFlagged }) => {
|
||||
const isPassed = status === 'Passed';
|
||||
|
||||
let themeStyles = 'from-sky-50 to-white border-sky-100 text-primary-blue';
|
||||
|
|
@ -705,10 +723,10 @@ const LeadAgentSummary: React.FC<{ status: OverallStatus, summary: string, onFla
|
|||
</h3>
|
||||
<button
|
||||
onClick={onFlag}
|
||||
className="text-slate-400 hover:text-red-500 p-2 rounded-full hover:bg-white/50 transition-colors"
|
||||
title="Flag as incorrect"
|
||||
className={`p-2 rounded-full transition-colors ${isFlagged ? 'text-red-500' : 'text-slate-400 hover:text-red-500 hover:bg-white/50'}`}
|
||||
title={isFlagged ? "Flagged as incorrect" : "Flag as incorrect"}
|
||||
>
|
||||
<FlagIcon className="h-5 w-5" />
|
||||
<FlagIcon className="h-5 w-5" filled={isFlagged} />
|
||||
</button>
|
||||
</div>
|
||||
<div className={`text-lg leading-relaxed ${isPassed ? 'text-emerald-800/80' : 'text-slate-700'}`}>
|
||||
|
|
@ -770,11 +788,20 @@ const FinancialPromotionSummary: React.FC<{ reason: string; summary: string }> =
|
|||
};
|
||||
|
||||
|
||||
export const FeedbackReport: React.FC<{
|
||||
export const FeedbackReport: React.FC<{
|
||||
feedback: AgentReview;
|
||||
onFlagSubmit: (agentName: string, comments: string) => void;
|
||||
onResolveSubmit: (agentName: string, issueText: string, reason: string) => void;
|
||||
}> = ({ feedback, onFlagSubmit, onResolveSubmit }) => {
|
||||
flaggedItems?: FlaggedItem[];
|
||||
proofName?: string;
|
||||
version?: number;
|
||||
}> = ({ feedback, onFlagSubmit, onResolveSubmit, flaggedItems = [], proofName, version }) => {
|
||||
const flaggedAgents = new Set(
|
||||
flaggedItems
|
||||
.filter(f => f.proofName === proofName && f.version === version)
|
||||
.map(f => f.agentFlagged)
|
||||
);
|
||||
|
||||
const [flagModalState, setFlagModalState] = useState<{ isOpen: boolean; agentName: string }>({
|
||||
isOpen: false,
|
||||
agentName: '',
|
||||
|
|
@ -790,8 +817,6 @@ export const FeedbackReport: React.FC<{
|
|||
|
||||
const handleSubmitFlag = (comments: string) => {
|
||||
onFlagSubmit(flagModalState.agentName, comments);
|
||||
alert(`Thank you for your feedback on the ${flagModalState.agentName}'s review. This has been logged for auditing.`);
|
||||
handleCloseFlagModal();
|
||||
};
|
||||
|
||||
const agentReviews = [
|
||||
|
|
@ -818,22 +843,24 @@ export const FeedbackReport: React.FC<{
|
|||
summary={feedback.leadAgentSummary}
|
||||
/>
|
||||
) : (
|
||||
<LeadAgentSummary
|
||||
status={feedback.overallStatus}
|
||||
summary={feedback.leadAgentSummary}
|
||||
<LeadAgentSummary
|
||||
status={feedback.overallStatus}
|
||||
summary={feedback.leadAgentSummary}
|
||||
onFlag={() => handleOpenFlagModal('Lead Agent')}
|
||||
isFlagged={flaggedAgents.has('Lead Agent')}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
{agentReviews.map(({ title, review }) => (
|
||||
<SubReviewCard
|
||||
<SubReviewCard
|
||||
key={title}
|
||||
title={`${title}`}
|
||||
title={`${title}`}
|
||||
review={review}
|
||||
onFlag={() => handleOpenFlagModal(title)}
|
||||
onResolve={(issueText, reason) => onResolveSubmit(title, issueText, reason)}
|
||||
isFlagged={flaggedAgents.has(title)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
export const FlagIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
|
||||
export const FlagIcon: React.FC<React.SVGProps<SVGSVGElement> & { filled?: boolean }> = ({ filled, ...props }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill={filled ? "currentColor" : "none"} viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3 3v1.5M3 21v-6m0 0l2.77-.693a9 9 0 016.208.682l.108.054a9 9 0 006.086.71l3.114-.732a48.524 48.524 0 01-.005-10.499l-3.11.732a9 9 0 01-6.085-.711l-.108-.054a9 9 0 00-6.208-.682L3 4.5M3 15V4.5" />
|
||||
</svg>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue