Catalyst / admin/Bid-Sentinel 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Bid-Sentinel

public

Bid Scrape and Tracking Application with AI Capability

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
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>
  );
}