Files
Aegis/frontend/src/components/test-detail/ResolveDisputeModal.tsx
T
kitos 19c4866103
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
feat(tests): dispute resolution UI — manager escalation + red/blue queue selector
2026-07-06 12:40:21 +02:00

113 lines
4.7 KiB
TypeScript

import { useState } from "react";
import { Shield, ShieldCheck, Loader2, X, XCircle } from "lucide-react";
// ── Props ──────────────────────────────────────────────────────────
interface ResolveDisputeModalProps {
isSubmitting: boolean;
onSubmit: (targetTeam: "red" | "blue", notes: string) => void;
onClose: () => void;
}
// ── Component ──────────────────────────────────────────────────────
export default function ResolveDisputeModal({
isSubmitting,
onSubmit,
onClose,
}: ResolveDisputeModalProps) {
const [targetTeam, setTargetTeam] = useState<"red" | "blue" | null>(null);
const [notes, setNotes] = useState("");
const canSubmit = targetTeam !== null && !isSubmitting;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="w-full max-w-lg rounded-xl border border-gray-800 bg-gray-900 shadow-xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-gray-800 px-6 py-4">
<div className="flex items-center gap-2">
<XCircle className="h-5 w-5 text-red-400" />
<h3 className="text-lg font-semibold text-white">Change Vote to Rejected</h3>
</div>
<button
onClick={onClose}
className="rounded p-1 text-gray-400 hover:bg-gray-800 hover:text-white"
>
<X className="h-5 w-5" />
</button>
</div>
{/* Body */}
<div className="space-y-5 px-6 py-5">
<p className="text-sm text-gray-400">
You're agreeing that this test needs rework. Instead of restarting from scratch,
pick which team's work is actually the problem the test goes straight back to
their queue and the other team's work is left untouched.
</p>
<div>
<h4 className="mb-2 text-sm font-medium text-gray-300">Send back to</h4>
<div className="flex gap-3">
<button
onClick={() => setTargetTeam("red")}
className={`flex flex-1 items-center justify-center gap-2 rounded-lg border p-3 text-sm font-medium transition-colors ${
targetTeam === "red"
? "border-orange-500 bg-orange-500/10 text-orange-400"
: "border-gray-700 bg-gray-800 text-gray-400 hover:border-gray-600"
}`}
>
<Shield className="h-4 w-4" />
Red Team Queue
</button>
<button
onClick={() => setTargetTeam("blue")}
className={`flex flex-1 items-center justify-center gap-2 rounded-lg border p-3 text-sm font-medium transition-colors ${
targetTeam === "blue"
? "border-indigo-500 bg-indigo-500/10 text-indigo-400"
: "border-gray-700 bg-gray-800 text-gray-400 hover:border-gray-600"
}`}
>
<ShieldCheck className="h-4 w-4" />
Blue Team Queue
</button>
</div>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-300">
Notes (optional)
</label>
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
rows={3}
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 placeholder-gray-500 focus:border-red-500 focus:outline-none focus:ring-1 focus:ring-red-500"
placeholder="What needs to be fixed..."
/>
</div>
</div>
{/* Footer */}
<div className="flex justify-end gap-3 border-t border-gray-800 px-6 py-4">
<button
onClick={onClose}
disabled={isSubmitting}
className="rounded-lg border border-gray-700 px-4 py-2 text-sm text-gray-400 hover:bg-gray-800 disabled:opacity-50"
>
Cancel
</button>
<button
onClick={() => targetTeam && onSubmit(targetTeam, notes)}
disabled={!canSubmit}
className="flex items-center gap-1.5 rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-500 disabled:opacity-50 transition-colors"
>
{isSubmitting && <Loader2 className="h-4 w-4 animate-spin" />}
Confirm Rejection
</button>
</div>
</div>
</div>
);
}