admin / Bid-Sentinel
publicBid Scrape and Tracking Application with AI Capability
Bid-Sentinel / bid-sentinel-v2 / frontend / src / components / OutcomeSelect.jsx
1966 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | import { useEffect, useState } from "react"; import { updateOutcome } from "../api/client"; const OPTIONS = ["Awaiting", "Won", "Lost", "No Bid", "Disregarded"]; const COLORS = { Awaiting: "bg-slate-100 text-slate-600 border-slate-300", Won: "bg-green-50 text-green-700 border-green-300", Lost: "bg-red-50 text-red-700 border-red-300", "No Bid": "bg-amber-50 text-amber-700 border-amber-300", Disregarded: "bg-slate-200 text-slate-500 border-slate-300", }; export default function OutcomeSelect({ tender, onChange }) { const [outcome, setOutcome] = useState(tender.outcome || "Awaiting"); const [saving, setSaving] = useState(false); const [error, setError] = useState(false); // Keep in sync when the row's outcome changes elsewhere (e.g. setting Bid // Status to "No Bid" auto-sets the outcome to "No Bid"). useEffect(() => { setOutcome(tender.outcome || "Awaiting"); }, [tender.outcome]); async function handleChange(e) { const next = e.target.value; const previous = outcome; setOutcome(next); // optimistic setSaving(true); setError(false); try { const updated = await updateOutcome(tender.id, next); onChange?.(updated); // bubbles up so KPI cards recompute } catch { setOutcome(previous); setError(true); } finally { setSaving(false); } } return ( <div className="flex items-center gap-2"> <select value={outcome} onChange={handleChange} disabled={saving} className={`rounded-md border px-2 py-1 text-sm font-medium outline-none transition focus:ring-2 focus:ring-brand/30 ${COLORS[outcome] || ""}`} > {OPTIONS.map((opt) => ( <option key={opt} value={opt}> {opt} </option> ))} </select> {saving && <span className="text-xs text-slate-400">saving…</span>} {error && <span className="text-xs text-red-500">failed</span>} </div> ); } |