admin / Bid-Sentinel
publicBid Scrape and Tracking Application with AI Capability
Bid-Sentinel / bid-sentinel-v2 / frontend / src / components / BidStatusSelect.jsx
1565 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 | import { useState } from "react"; import { updateBidStatus } from "../api/client"; const OPTIONS = ["Pending", "Bid", "No Bid"]; const COLORS = { Pending: "bg-slate-100 text-slate-700 border-slate-300", Bid: "bg-green-50 text-green-700 border-green-300", "No Bid": "bg-red-50 text-red-700 border-red-300", }; export default function BidStatusSelect({ tender, onChange }) { const [status, setStatus] = useState(tender.bid_status); const [saving, setSaving] = useState(false); const [error, setError] = useState(false); async function handleChange(e) { const next = e.target.value; const previous = status; setStatus(next); // optimistic update setSaving(true); setError(false); try { const updated = await updateBidStatus(tender.id, next); onChange?.(updated); } catch { setStatus(previous); // rollback on failure setError(true); } finally { setSaving(false); } } return ( <div className="flex items-center gap-2"> <select value={status} 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[status] || ""}`} > {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> ); } |