Skip to content

EOL Upgrade #129

@rogelambrocio-byte

Description

@rogelambrocio-byte

import { useState, useMemo } from "react";

const STATUS_OPTIONS = [
"Completed Upgrade",
"Completed Traffic Swing",
"Decommed",
"For Decom (Housekeeping)",
"For Decom (Upgrade Not Needed)",
"For Decomm",
"Access",
"In Progress",
"Pending",
"",
];

const STATUS_COLORS = {
"Completed Upgrade": { bg: "#1a3a5c", text: "#7ec8f4", dot: "#4da6d8" },
"Completed Traffic Swing": { bg: "#0e2a40", text: "#5bbcf0", dot: "#3399cc" },
"Decommed": { bg: "#1a3d1a", text: "#7dcc7d", dot: "#4caf50" },
"For Decom (Housekeeping)": { bg: "#2a3d14", text: "#b5e07a", dot: "#8bc34a" },
"For Decom (Upgrade Not Needed)": { bg: "#3d2e00", text: "#f5c842", dot: "#e6a817" },
"For Decomm": { bg: "#3d2e00", text: "#f5c842", dot: "#e6a817" },
"Access": { bg: "#3d1a2e", text: "#f07ab5", dot: "#e91e8c" },
"In Progress": { bg: "#1a2a3d", text: "#80b3ff", dot: "#4488ff" },
"Pending": { bg: "#2d2d2d", text: "#aaaaaa", dot: "#888888" },
"": { bg: "#1e1e1e", text: "#666", dot: "#555" },
};

const ENV_COLORS = {
Prod: { bg: "#2a1a1a", text: "#ff7c7c", border: "#c0392b" },
NonProd: { bg: "#1a2a1a", text: "#7ccc7c", border: "#27ae60" },
};

const RAW_DATA = [
{ name: "amex-db-cluster4pro", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "amex-db-cluster4pro-replica", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "bifrost-prod", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "buyload-test", tribe: "B2C", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "cluster5-shared-db-uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "ctr4pushnotifsvcdbpro", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "ctr4pushnotifsvcdbpro-replica", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "db-optimization-prod", tribe: "DBOps", environment: "NonProd", status: "", notes: "" },
{ name: "devtools-perf", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "gloanrds-prd", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "gloanrds-prd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "golbat", tribe: "B2B", environment: "Prod", status: "Decommed", notes: "" },
{ name: "golbat-replica", tribe: "B2B", environment: "Prod", status: "Decommed", notes: "" },
{ name: "knowhere", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "konga", tribe: "Technology", environment: "Prod", status: "Access", notes: "" },
{ name: "konga-kong-cluster-production", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "konga-kong-cluster-uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "l2-ackerman", tribe: "L2", environment: "Prod", status: "", notes: "" },
{ name: "marvel", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "octdbprod", tribe: "Enterprise Services", environment: "Prod", status: "", notes: "" },
{ name: "octdbprod-old1", tribe: "Enterprise Services", environment: "Prod", status: "Decommed", notes: "" },
{ name: "partner-notifs-prd", tribe: "Lending", environment: "Prod", status: "Access", notes: "binlog_format (Mixed)" },
{ name: "partner-notifs-prd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "payments-pickaxe-production", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "payments-pickaxe-replica-production", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "promocode", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "promocode-replica", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "rcbc", tribe: "Funds", environment: "Prod", status: "", notes: "No objects" },
{ name: "rcbc-replica", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "sem-automation-rds", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "sendmoney-kkb-replica", tribe: "Funds", environment: "Prod", status: "For Decomm", notes: "" },
{ name: "sendmoney-perf", tribe: "Funds", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "shared-db-cluster1uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster1uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster2uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster2uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster4uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster4uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-rds-sit", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-rds-sit-16-june-test", tribe: "Technology", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "sqlag-rds-uat", tribe: "DBOps", environment: "NonProd", status: "", notes: "MTCSD-287542" },
{ name: "telcoscore-db-prod", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "telcoscore-db-prod-replica", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "telcoscore-db-uat", tribe: "New Business", environment: "NonProd", status: "", notes: "" },
{ name: "unicorn-rds-sit", tribe: "New Business", environment: "NonProd", status: "", notes: "" },
{ name: "user-consent-serv", tribe: "Customer and Integration", environment: "Prod", status: "", notes: "" },
{ name: "user-consent-serv-replica", tribe: "Customer and Integration", environment: "Prod", status: "", notes: "" },
{ name: "ussd", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "ussd-replica", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "victoria-prd", tribe: "Lending", environment: "Prod", status: "Access", notes: "" },
{ name: "victoria-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "vmacqdpd01", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "vmacqdpd01-replica", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "vmbaiddv01", tribe: "Funds", environment: "NonProd", status: "", notes: "" },
{ name: "vmcladpd01", tribe: "Segments", environment: "Prod", status: "", notes: "" },
{ name: "vmcladpd02", tribe: "Segments", environment: "Prod", status: "", notes: "" },
{ name: "vmcladut01", tribe: "Segments", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
{ name: "vmcladut02", tribe: "Segments", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
{ name: "vmpokappdpd03", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokappdpd03-replica", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdisdpd03", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdisdpd03-replica", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdispduat01", tribe: "B2B", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "vmpokdispduat01-old1", tribe: "B2B", environment: "NonProd", status: "Decommed", notes: "Done April 23" },
{ name: "vmpokpduat01", tribe: "B2B", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "vmrqrddv01", tribe: "Segments", environment: "NonProd", status: "", notes: "" },
{ name: "vmrqrdpd01", tribe: "Segments", environment: "Prod", status: "Access", notes: "" },
{ name: "wiirdsmysql", tribe: "Lending", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "wiirdsmysql-replica", tribe: "Lending", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "wiirdsprd", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "wiirdsprd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "wire-sms-production", tribe: "Technology", environment: "Prod", status: "", notes: "binlog_format (Mixed)" },
{ name: "wire-sms-production-replica", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "xrecon-logs-uat", tribe: "Enterprise Services", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
];

const TRIBES = [...new Set(RAW_DATA.map(r => r.tribe))].sort();

function StatusBadge({ status }) {
const c = STATUS_COLORS[status] || STATUS_COLORS[""];
return (
<span style={{
background: c.bg,
color: c.text,
border: 1px solid ${c.dot}33,
borderRadius: 4,
padding: "2px 8px",
fontSize: 11,
fontFamily: "'IBM Plex Mono', monospace",
whiteSpace: "nowrap",
display: "inline-flex",
alignItems: "center",
gap: 5,
}}>
<span style={{ width: 6, height: 6, borderRadius: "50%", background: c.dot, flexShrink: 0 }} />
{status || "—"}

);
}

function EnvBadge({ env }) {
const c = ENV_COLORS[env] || { bg: "#222", text: "#aaa", border: "#555" };
return (
<span style={{
background: c.bg,
color: c.text,
border: 1px solid ${c.border}55,
borderRadius: 3,
padding: "1px 7px",
fontSize: 11,
fontFamily: "'IBM Plex Mono', monospace",
letterSpacing: "0.03em",
}}>{env}
);
}

export default function App() {
const [data, setData] = useState(RAW_DATA.map((r, i) => ({ ...r, id: i })));
const [editingId, setEditingId] = useState(null);
const [editValues, setEditValues] = useState({});
const [filterTribe, setFilterTribe] = useState("All");
const [filterEnv, setFilterEnv] = useState("All");
const [filterStatus, setFilterStatus] = useState("All");
const [search, setSearch] = useState("");
const [showAddRow, setShowAddRow] = useState(false);
const [newRow, setNewRow] = useState({ name: "", tribe: "B2C", environment: "Prod", status: "", notes: "" });
const [activeTab, setActiveTab] = useState("table");

const filtered = useMemo(() => data.filter(r => {
if (filterTribe !== "All" && r.tribe !== filterTribe) return false;
if (filterEnv !== "All" && r.environment !== filterEnv) return false;
if (filterStatus !== "All" && r.status !== filterStatus) return false;
if (search && !r.name.toLowerCase().includes(search.toLowerCase()) && !r.tribe.toLowerCase().includes(search.toLowerCase())) return false;
return true;
}), [data, filterTribe, filterEnv, filterStatus, search]);

const summary = useMemo(() => {
const counts = {};
data.forEach(r => {
const k = r.status || "Pending";
counts[k] = (counts[k] || 0) + 1;
});
return counts;
}, [data]);

const tribeBreakdown = useMemo(() => {
const counts = {};
data.forEach(r => {
counts[r.tribe] = (counts[r.tribe] || 0) + 1;
});
return Object.entries(counts).sort((a, b) => b[1] - a[1]);
}, [data]);

function startEdit(row) {
setEditingId(row.id);
setEditValues({ status: row.status, notes: row.notes, tribe: row.tribe, environment: row.environment });
}

function saveEdit(id) {
setData(prev => prev.map(r => r.id === id ? { ...r, ...editValues } : r));
setEditingId(null);
}

function deleteRow(id) {
setData(prev => prev.filter(r => r.id !== id));
}

function addRow() {
if (!newRow.name.trim()) return;
setData(prev => [...prev, { ...newRow, id: Date.now() }]);
setNewRow({ name: "", tribe: "B2C", environment: "Prod", status: "", notes: "" });
setShowAddRow(false);
}

const allStatuses = [...new Set(data.map(r => r.status))].sort();

return (
<div style={{
minHeight: "100vh",
background: "#0d0d0d",
color: "#e0e0e0",
fontFamily: "'IBM Plex Sans', 'Segoe UI', sans-serif",
padding: "0",
}}>
{/* Header */}
<div style={{
background: "linear-gradient(135deg, #0a1628 0%, #0d1f3c 50%, #091a2e 100%)",
borderBottom: "1px solid #1e3a5f",
padding: "20px 28px 0",
}}>
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 16 }}>


<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
<span style={{
background: "#1a4a8a",
color: "#7ec8f4",
fontSize: 10,
padding: "2px 8px",
borderRadius: 2,
letterSpacing: "0.12em",
fontFamily: "'IBM Plex Mono', monospace",
fontWeight: 600,
}}>MYSQL 8.4
<span style={{ color: "#3a6a9a", fontSize: 11 }}>RDS UPGRADE TRACKER

<h1 style={{
margin: 0,
fontSize: 22,
fontWeight: 700,
color: "#d4e9ff",
letterSpacing: "-0.01em",
}}>MySQL RDS Upgrade Dashboard
<p style={{ margin: "4px 0 0", fontSize: 12, color: "#4a7aaa" }}>
Total: <strong style={{ color: "#7ec8f4" }}>{data.length} instances tracked



<button
onClick={() => setShowAddRow(!showAddRow)}
style={{
background: "#1a4a8a",
color: "#7ec8f4",
border: "1px solid #2a6ab5",
borderRadius: 5,
padding: "7px 14px",
fontSize: 12,
cursor: "pointer",
fontFamily: "inherit",
display: "flex",
alignItems: "center",
gap: 6,
}}>
+ Add Instance

    {/* Tabs */}
    <div style={{ display: "flex", gap: 0 }}>
      {["table", "summary"].map(tab => (
        <button key={tab} onClick={() => setActiveTab(tab)} style={{
          background: activeTab === tab ? "#0d0d0d" : "transparent",
          color: activeTab === tab ? "#7ec8f4" : "#4a7aaa",
          border: "none",
          borderTop: activeTab === tab ? "2px solid #4a9adf" : "2px solid transparent",
          borderLeft: "1px solid " + (activeTab === tab ? "#1e3a5f" : "transparent"),
          borderRight: "1px solid " + (activeTab === tab ? "#1e3a5f" : "transparent"),
          padding: "8px 18px",
          fontSize: 12,
          fontFamily: "inherit",
          cursor: "pointer",
          letterSpacing: "0.06em",
          textTransform: "uppercase",
        }}>
          {tab === "table" ? "Instance Table" : "Summary View"}
        </button>
      ))}
    </div>
  </div>

  <div style={{ padding: "20px 28px" }}>

    {/* Add Row Form */}
    {showAddRow && (
      <div style={{
        background: "#111c2e",
        border: "1px solid #1e3a5f",
        borderRadius: 8,
        padding: "16px",
        marginBottom: 20,
        display: "grid",
        gridTemplateColumns: "2fr 1fr 1fr 1.5fr 2fr auto",
        gap: 10,
        alignItems: "end",
      }}>
        {[
          ["RDS Name", "name", "text"],
          ["Tribe", "tribe", "tribe-select"],
          ["Environment", "environment", "env-select"],
          ["Status", "status", "status-select"],
          ["Notes", "notes", "text"],
        ].map(([label, key, type]) => (
          <div key={key}>
            <div style={{ fontSize: 10, color: "#4a7aaa", marginBottom: 4, letterSpacing: "0.08em" }}>{label}</div>
            {type === "text" ? (
              <input value={newRow[key]} onChange={e => setNewRow(p => ({ ...p, [key]: e.target.value }))}
                style={{ width: "100%", background: "#0d1f3c", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 8px", fontSize: 12, fontFamily: "'IBM Plex Mono', monospace", boxSizing: "border-box" }} />
            ) : type === "tribe-select" ? (
              <select value={newRow[key]} onChange={e => setNewRow(p => ({ ...p, [key]: e.target.value }))}
                style={{ width: "100%", background: "#0d1f3c", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 8px", fontSize: 12, fontFamily: "inherit" }}>
                {TRIBES.map(t => <option key={t}>{t}</option>)}
              </select>
            ) : type === "env-select" ? (
              <select value={newRow[key]} onChange={e => setNewRow(p => ({ ...p, [key]: e.target.value }))}
                style={{ width: "100%", background: "#0d1f3c", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 8px", fontSize: 12, fontFamily: "inherit" }}>
                <option>Prod</option><option>NonProd</option>
              </select>
            ) : (
              <select value={newRow[key]} onChange={e => setNewRow(p => ({ ...p, [key]: e.target.value }))}
                style={{ width: "100%", background: "#0d1f3c", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 8px", fontSize: 12, fontFamily: "inherit" }}>
                {STATUS_OPTIONS.map(s => <option key={s} value={s}>{s || "— none —"}</option>)}
              </select>
            )}
          </div>
        ))}
        <button onClick={addRow} style={{
          background: "#1a4a8a", color: "#7ec8f4", border: "1px solid #2a6ab5",
          borderRadius: 4, padding: "7px 14px", fontSize: 12, cursor: "pointer", fontFamily: "inherit"
        }}>Add</button>
      </div>
    )}

    {activeTab === "table" && (
      <>
        {/* Filters */}
        <div style={{ display: "flex", gap: 10, marginBottom: 16, flexWrap: "wrap", alignItems: "center" }}>
          <input placeholder="🔍  Search name or tribe…" value={search} onChange={e => setSearch(e.target.value)}
            style={{ background: "#111c2e", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 5, padding: "7px 12px", fontSize: 12, fontFamily: "inherit", width: 220 }} />
          {[
            ["Tribe", filterTribe, setFilterTribe, ["All", ...TRIBES]],
            ["Env", filterEnv, setFilterEnv, ["All", "Prod", "NonProd"]],
            ["Status", filterStatus, setFilterStatus, ["All", ...allStatuses]],
          ].map(([label, val, setter, opts]) => (
            <div key={label} style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <span style={{ fontSize: 11, color: "#4a7aaa", letterSpacing: "0.06em" }}>{label}</span>
              <select value={val} onChange={e => setter(e.target.value)}
                style={{ background: "#111c2e", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 10px", fontSize: 12, fontFamily: "inherit" }}>
                {opts.map(o => <option key={o} value={o}>{o || "— none —"}</option>)}
              </select>
            </div>
          ))}
          <span style={{ marginLeft: "auto", fontSize: 11, color: "#3a6a9a" }}>
            {filtered.length} of {data.length} instances
          </span>
        </div>

        {/* Table */}
        <div style={{ overflowX: "auto", borderRadius: 8, border: "1px solid #1a3050" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
            <thead>
              <tr style={{ background: "#0a1a2e", borderBottom: "1px solid #1e3a5f" }}>
                {["#", "RDS Instance Name", "Tribe", "Environment", "Status", "Notes", "Actions"].map(h => (
                  <th key={h} style={{
                    padding: "10px 12px", textAlign: "left", color: "#4a8ab5",
                    fontWeight: 600, fontSize: 10, letterSpacing: "0.1em",
                    textTransform: "uppercase", whiteSpace: "nowrap",
                    borderRight: "1px solid #111c2e",
                  }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {filtered.map((row, idx) => (
                <tr key={row.id} style={{
                  background: idx % 2 === 0 ? "#0d0d0d" : "#0f1a28",
                  borderBottom: "1px solid #141e2e",
                  transition: "background 0.15s",
                }}
                  onMouseEnter={e => e.currentTarget.style.background = "#111c2e"}
                  onMouseLeave={e => e.currentTarget.style.background = idx % 2 === 0 ? "#0d0d0d" : "#0f1a28"}>
                  <td style={{ padding: "8px 12px", color: "#3a6a9a", fontFamily: "'IBM Plex Mono', monospace", fontSize: 11 }}>{idx + 1}</td>
                  <td style={{ padding: "8px 12px", color: "#b8d4f0", fontFamily: "'IBM Plex Mono', monospace", fontSize: 11 }}>{row.name}</td>

                  {/* Tribe */}
                  <td style={{ padding: "8px 12px" }}>
                    {editingId === row.id ? (
                      <select value={editValues.tribe} onChange={e => setEditValues(p => ({ ...p, tribe: e.target.value }))}
                        style={{ background: "#0d1f3c", border: "1px solid #2a6ab5", color: "#cde", borderRadius: 3, padding: "3px 6px", fontSize: 11, fontFamily: "inherit" }}>
                        {TRIBES.map(t => <option key={t}>{t}</option>)}
                      </select>
                    ) : <span style={{ color: "#9ab8d8", fontSize: 12 }}>{row.tribe}</span>}
                  </td>

                  {/* Environment */}
                  <td style={{ padding: "8px 12px" }}>
                    {editingId === row.id ? (
                      <select value={editValues.environment} onChange={e => setEditValues(p => ({ ...p, environment: e.target.value }))}
                        style={{ background: "#0d1f3c", border: "1px solid #2a6ab5", color: "#cde", borderRadius: 3, padding: "3px 6px", fontSize: 11, fontFamily: "inherit" }}>
                        <option>Prod</option><option>NonProd</option>
                      </select>
                    ) : <EnvBadge env={row.environment} />}
                  </td>

                  {/* Status */}
                  <td style={{ padding: "8px 12px" }}>
                    {editingId === row.id ? (
                      <select value={editValues.status} onChange={e => setEditValues(p => ({ ...p, status: e.target.value }))}
                        style={{ background: "#0d1f3c", border: "1px solid #2a6ab5", color: "#cde", borderRadius: 3, padding: "3px 6px", fontSize: 11, fontFamily: "inherit" }}>
                        {STATUS_OPTIONS.map(s => <option key={s} value={s}>{s || "— none —"}</option>)}
                      </select>
                    ) : <StatusBadge status={row.status} />}
                  </td>

                  {/* Notes */}
                  <td style={{ padding: "8px 12px", color: "#5a8aaa", fontSize: 11, fontStyle: "italic" }}>
                    {editingId === row.id ? (
                      <input value={editValues.notes} onChange={e => setEditValues(p => ({ ...p, notes: e.target.value }))}
                        style={{ background: "#0d1f3c", border: "1px solid #2a6ab5", color: "#cde", borderRadius: 3, padding: "3px 6px", fontSize: 11, fontFamily: "'IBM Plex Mono', monospace", width: 140 }} />
                    ) : row.notes || ""}
                  </td>

                  {/* Actions */}
                  <td style={{ padding: "8px 12px", whiteSpace: "nowrap" }}>
                    {editingId === row.id ? (
                      <div style={{ display: "flex", gap: 6 }}>
                        <button onClick={() => saveEdit(row.id)} style={{ background: "#1a4a8a", color: "#7ec8f4", border: "none", borderRadius: 3, padding: "3px 10px", fontSize: 11, cursor: "pointer" }}>Save</button>
                        <button onClick={() => setEditingId(null)} style={{ background: "#1a2a3a", color: "#7aaa9a", border: "none", borderRadius: 3, padding: "3px 8px", fontSize: 11, cursor: "pointer" }}>✕</button>
                      </div>
                    ) : (
                      <div style={{ display: "flex", gap: 6 }}>
                        <button onClick={() => startEdit(row)} style={{ background: "#0d1f3c", color: "#4a8ab5", border: "1px solid #1e3a5f", borderRadius: 3, padding: "3px 8px", fontSize: 11, cursor: "pointer" }}>Edit</button>
                        <button onClick={() => deleteRow(row.id)} style={{ background: "#1a0a0a", color: "#aa4444", border: "1px solid #3a1a1a", borderRadius: 3, padding: "3px 8px", fontSize: 11, cursor: "pointer" }}>Del</button>
                      </div>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </>
    )}

    {activeTab === "summary" && (
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
        {/* Status Summary */}
        <div style={{ background: "#0a1628", border: "1px solid #1e3a5f", borderRadius: 8, padding: 20 }}>
          <h3 style={{ margin: "0 0 16px", color: "#7ec8f4", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>Overall Status</h3>
          {Object.entries(summary).sort((a, b) => b[1] - a[1]).map(([status, count]) => {
            const c = STATUS_COLORS[status] || STATUS_COLORS[""];
            const pct = ((count / data.length) * 100).toFixed(1);
            return (
              <div key={status} style={{ marginBottom: 12 }}>
                <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
                  <span style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12 }}>
                    <span style={{ width: 8, height: 8, borderRadius: "50%", background: c.dot }} />
                    <span style={{ color: c.text }}>{status || "—"}</span>
                  </span>
                  <span style={{ color: "#7ec8f4", fontFamily: "'IBM Plex Mono', monospace", fontSize: 12 }}>
                    {count} <span style={{ color: "#3a6a9a" }}>({pct}%)</span>
                  </span>
                </div>
                <div style={{ height: 4, background: "#0d1f3c", borderRadius: 2 }}>
                  <div style={{ height: 4, width: `${pct}%`, background: c.dot, borderRadius: 2, transition: "width 0.5s" }} />
                </div>
              </div>
            );
          })}
        </div>

        {/* Tribe Breakdown */}
        <div style={{ background: "#0a1628", border: "1px solid #1e3a5f", borderRadius: 8, padding: 20 }}>
          <h3 style={{ margin: "0 0 16px", color: "#7ec8f4", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>By Tribe</h3>
          {tribeBreakdown.map(([tribe, count]) => {
            const pct = ((count / data.length) * 100).toFixed(1);
            return (
              <div key={tribe} style={{ marginBottom: 10 }}>
                <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 3 }}>
                  <span style={{ color: "#9ab8d8", fontSize: 12 }}>{tribe}</span>
                  <span style={{ color: "#7ec8f4", fontFamily: "'IBM Plex Mono', monospace", fontSize: 12 }}>
                    {count} <span style={{ color: "#3a6a9a" }}>({pct}%)</span>
                  </span>
                </div>
                <div style={{ height: 3, background: "#0d1f3c", borderRadius: 2 }}>
                  <div style={{ height: 3, width: `${pct}%`, background: "#2a6ab5", borderRadius: 2, transition: "width 0.5s" }} />
                </div>
              </div>
            );
          })}
        </div>

        {/* Env split */}
        <div style={{ background: "#0a1628", border: "1px solid #1e3a5f", borderRadius: 8, padding: 20 }}>
          <h3 style={{ margin: "0 0 16px", color: "#7ec8f4", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>Prod vs NonProd</h3>
          {["Prod", "NonProd"].map(env => {
            const count = data.filter(r => r.environment === env).length;
            const pct = ((count / data.length) * 100).toFixed(1);
            const c = ENV_COLORS[env];
            return (
              <div key={env} style={{ marginBottom: 14 }}>
                <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
                  <span style={{ color: c.text, fontSize: 13 }}>{env}</span>
                  <span style={{ color: "#7ec8f4", fontFamily: "'IBM Plex Mono', monospace", fontSize: 12 }}>
                    {count} <span style={{ color: "#3a6a9a" }}>({pct}%)</span>
                  </span>
                </div>
                <div style={{ height: 6, background: "#0d1f3c", borderRadius: 3 }}>
                  <div style={{ height: 6, width: `${pct}%`, background: c.border, borderRadius: 3 }} />
                </div>
              </div>
            );
          })}
        </div>

        {/* Quick stats */}
        <div style={{ background: "#0a1628", border: "1px solid #1e3a5f", borderRadius: 8, padding: 20 }}>
          <h3 style={{ margin: "0 0 16px", color: "#7ec8f4", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>Quick Stats</h3>
          {[
            ["Total Instances", data.length, "#7ec8f4"],
            ["Completed Upgrade", data.filter(r => r.status === "Completed Upgrade").length, "#4da6d8"],
            ["Decommed", data.filter(r => r.status === "Decommed").length, "#4caf50"],
            ["For Decomm", data.filter(r => r.status?.startsWith("For Decomm")).length, "#e6a817"],
            ["No Status Yet", data.filter(r => !r.status).length, "#888"],
          ].map(([label, val, color]) => (
            <div key={label} style={{ display: "flex", justifyContent: "space-between", padding: "8px 0", borderBottom: "1px solid #111c2e" }}>
              <span style={{ color: "#9ab8d8", fontSize: 12 }}>{label}</span>
              <span style={{ color, fontFamily: "'IBM Plex Mono', monospace", fontSize: 14, fontWeight: 700 }}>{val}</span>
            </div>
          ))}
        </div>
      </div>
    )}
  </div>
</div>

);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions