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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions