// screens-admin-shell.jsx — Admin shell (tab bar, top strip) + Now screen
const { fmtIDR, findClass, findCoach, findMember, DAYS_SHORT, DAYS_LONG, TODAY_DAY, TODAY_HOUR, TODAY_MIN, DATES_THIS_WEEK } = window.NADI_HELPERS;
// ── Admin tab bar (4 tabs, distinct from member bar) ─────────────────
const ADMIN_TAB_DEFS = [
{ id: "now", label: "Now" },
{ id: "schedule", label: "Schedule" },
{ id: "people", label: "People" },
{ id: "more", label: "More" },
];
function AdminTabIcon({ id, active }) {
const stroke = active ? "var(--bg)" : "rgba(248,241,225,0.55)";
const sw = 1.4;
if (id === "now") return (
);
if (id === "schedule") return (
);
if (id === "people") return (
);
if (id === "more") return (
);
return null;
}
function AdminTabBar({ tab, setTab }) {
return (
);
}
// ── Admin top strip (always visible at top) ────────────────────────
function AdminTopStrip({ admin, onExitAdmin }) {
return (
staff mode · {admin.role.toLowerCase()}
);
}
// ── NOW SCREEN ─────────────────────────────────────────────────────
function AdminNowScreen({ admin, openMember, openSession }) {
const today = window.NADI_ADMIN.todaysSessions();
const liveOrUpcoming = today.filter((s) => true);
const rev = window.NADI_ADMIN.TODAY_REVENUE;
const yest = window.NADI_ADMIN.YESTERDAY_REVENUE;
const diff = rev - yest;
const diffPct = Math.round((diff / yest) * 100);
return (
{/* Greeting */}
{DAYS_LONG[TODAY_DAY].toLowerCase()} · {String(DATES_THIS_WEEK[TODAY_DAY]).padStart(2, "0")} {new Date().toLocaleDateString("en-GB", { month: "short" }).toLowerCase()}
{String(TODAY_HOUR).padStart(2, "0")}:{String(TODAY_MIN).padStart(2, "0")} · live
The floor, now.
{/* Revenue pulse */}
today · gross revenue
{fmtIDR(rev)}
0 ? "var(--primary)" : "rgba(248,241,225,0.55)"} style={{ marginTop: 6 }}>
{diff > 0 ? "↑" : "↓"} {Math.abs(diffPct)}% vs yesterday · {fmtIDR(yest)}
{/* Hourly pulse */}
{/* Live class strip */}
today's classes
{today.length} sessions
{today.map((s) => openSession && openSession(s)} />)}
{/* Churn alerts */}
{/* Quick actions */}
);
}
function HourlyPulse({ rows }) {
const max = Math.max(1, ...rows.map((r) => r.v));
const nowH = new Date().getHours();
return (
{rows.map((r) => {
const isPast = parseInt(r.h, 10) <= nowH;
const h = r.v > 0 ? Math.max(4, Math.round((r.v / max) * 50)) : 2;
return (
0 ? "var(--primary)" : (isPast ? "rgba(248,241,225,0.16)" : "rgba(248,241,225,0.08)"),
opacity: r.v > 0 || isPast ? 1 : 0.6,
}} />
);
})}
{rows.map((r, i) => (
{(i % 2 === 0) ? r.h : ""}
))}
);
}
function LiveSessionRow({ s, onClick }) {
const c = findClass(s.classId), co = findCoach(s.coachId);
const checkin = (window.NADI_ADMIN.CHECKINS && window.NADI_ADMIN.CHECKINS[s.id]) || { checked: 0, ready: s.booked };
const nowTime = `${String(TODAY_HOUR).padStart(2, "0")}:${String(TODAY_MIN).padStart(2, "0")}`;
const isPast = s.time < nowTime;
const isNow = !isPast && s.time <= `${String(TODAY_HOUR + 1).padStart(2, "0")}:${String(TODAY_MIN + 30).padStart(2, "0")}`;
const pct = Math.round((checkin.checked / s.booked) * 100) || 0;
return (
);
}
function ChurnAlerts({ openMember }) {
const list = window.NADI_ADMIN.CHURN_LIST;
return (
churn watch · 3
lapsed members
{list.slice(0, 3).map((m, i) => (
))}
);
}
function QuickAction({ icon, label, sub, onClick }) {
return (
);
}
function QAIcon({ id }) {
const props = { width: 18, height: 18, viewBox: "0 0 20 20", fill: "none", stroke: "var(--primary)", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round" };
if (id === "walkin") return
;
if (id === "book") return
;
if (id === "cancel") return
;
if (id === "qr") return
;
return null;
}
Object.assign(window, { ADMIN_TAB_DEFS, AdminTabBar, AdminTopStrip, AdminNowScreen, LiveSessionRow });