// screens-admin-schedule.jsx — Admin schedule ops + session edit sheet
const { fmtIDR, findClass, findCoach, DAYS_SHORT, DAYS_LONG, DATES_THIS_WEEK, TODAY_DAY } = window.NADI_HELPERS;
function AdminScheduleScreen({ openSession, openAddSession, showToast }) {
const [day, setDay] = useState(TODAY_DAY);
const sessions = window.NADI.SCHEDULE.filter((s) => s.day === day).sort((a, b) => a.time.localeCompare(b.time));
// Total this day
const totalBooked = sessions.reduce((s, x) => s + x.booked, 0);
const totalCap = sessions.reduce((s, x) => s + x.capacity, 0);
const fillPct = Math.round((totalBooked / totalCap) * 100);
return (
{/* Header */}
schedule · this week
{day === TODAY_DAY ? <>Today, {DAYS_LONG[day].toLowerCase()}.> : <>{DAYS_LONG[day]}.>}
{sessions.length} sessions · {totalBooked}/{totalCap} seats ({fillPct}% full)
{/* Sessions list */}
{sessions.map((s) =>
openSession(s)} />)}
);
}
function AdminDayStrip({ day, setDay }) {
return (
{DAYS_SHORT.map((d, i) => {
const active = i === day;
const isToday = i === TODAY_DAY;
return (
);
})}
);
}
function AdminSessionRow({ s, onClick }) {
const c = findClass(s.classId), co = findCoach(s.coachId);
const left = s.capacity - s.booked;
const full = left === 0;
const pct = Math.round((s.booked / s.capacity) * 100);
return (
);
}
// ── Session edit sheet (used from admin schedule + now) ────────────
function AdminSessionSheet({ session, onClose, showToast, openRoster }) {
if (!session) return null;
const c = findClass(session.classId), co = findCoach(session.coachId);
const checkin = (window.NADI_ADMIN.CHECKINS && window.NADI_ADMIN.CHECKINS[session.id]) || { checked: 0 };
const isPast = session.day < TODAY_DAY || (session.day === TODAY_DAY && session.time < "14:08");
const pct = Math.round((session.booked / session.capacity) * 100);
return (
{DAYS_LONG[session.day].toLowerCase()} · {session.time} · {c.duration}m
{c.name}
with {co.name.toLowerCase()} · {session.roomShort.toLowerCase()}
{/* Capacity bar */}
occupancy
{session.booked}/{session.capacity} · {pct}%
{/* Detail grid */}
0 ? `${session.waitlistCount} waiting` : "—"} sub={session.waitlistCount > 0 ? "promote on cancel" : "open"} />
{/* Quick actions */}
{/* Notify attendees */}
notify attendees
Push a quick note to everyone booked — late teacher, room change, weather.
);
}
// ── Roster sheet (member list for a session) ──────────────────────
function AdminRosterSheet({ session, onClose, openMember, showToast }) {
if (!session) return null;
const c = findClass(session.classId);
// Mock roster — take the friends + a few members
const friendIds = session.friends;
const friendMembers = friendIds.map((fid) => {
// Friends map approximately to members by initials
const f = window.NADI.FRIENDS.find((x) => x.id === fid);
if (!f) return null;
return { id: fid, name: f.name + " " + (f.id === "f1" ? "Putri" : ""), initials: f.initials, tone: f.tone };
}).filter(Boolean);
const extra = window.NADI_ADMIN.MEMBERS.slice(0, session.booked - friendMembers.length).map((m) => ({
id: m.id, name: m.name, initials: m.initials, tone: m.tone, memberId: m.id,
}));
const roster = [...friendMembers, ...extra];
const dateStr = session.date ? window.NADI_HELPERS.parseLocalDate(session.date).toLocaleDateString("en-GB", { day: "2-digit", month: "short" }).toLowerCase() : "";
const nameSuffix = dateStr ? ` (${dateStr})` : "";
return (
roster · {DAYS_LONG[session.day].toLowerCase()} · {session.time}
{c.name}
{session.booked} booked · {session.capacity - session.booked} open
{roster.map((r, i) => (
))}
showToast && showToast("Walk-in dialog opened.")}>+ Add walk-in
);
}
// ── Add session sheet ────────────────────────────────────────────
function AdminAddSessionSheet({ open, onClose, showToast }) {
const [classId, setClassId] = useState("hatha");
const [coachId, setCoachId] = useState("ayu");
const [day, setDay] = useState(TODAY_DAY);
const [time, setTime] = useState("18:00");
const [room, setRoom] = useState(0);
const [capacity, setCapacity] = useState(14);
return (
schedule · add session
close ×
New session.
class type
{window.NADI.CLASS_TYPES.map((c) => (
setClassId(c.id)}>{c.name.toLowerCase()}
))}
teacher
{window.NADI.COACHES.map((co) => (
setCoachId(co.id)}>{co.firstName.toLowerCase()}
))}
day
{DAYS_SHORT.map((d, i) => (
setDay(i)}>{d.toLowerCase()}
))}
room
{window.NADI.STUDIOS.map((st, i) => (
setRoom(i)}>{st.name.toLowerCase()}
))}
Cancel
{
const token = localStorage.getItem("nadi_token");
if (!token) return;
const dateStr = "2026-05-" + String(DATES_THIS_WEEK[day]).padStart(2, "0");
const payload = {
date: dateStr,
time: time,
class_id: classId,
coach_id: coachId,
room: window.NADI.STUDIOS[room].full,
capacity: capacity,
booked: 0
};
try {
const res = await fetch("/api/admin/sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify(payload)
});
if (res.ok) {
showToast && showToast("Session created.");
if (window.NADI_RELOAD_SCHEDULE) {
await window.NADI_RELOAD_SCHEDULE();
}
onClose();
} else {
const err = await res.json();
showToast && showToast(err.detail || "Failed to create session.");
}
} catch (e) {
console.error(e);
showToast && showToast("Error connecting to server.");
}
}}>Add session
);
}
Object.assign(window, {
AdminScheduleScreen, AdminSessionRow,
AdminSessionSheet, AdminRosterSheet, AdminAddSessionSheet,
});