/* ============================================================ Manager · Members roster The tour manager reviews each member's GHIN + Handicap Index, carries last week's value forward, confirms or edits it, and the change is saved back to the member's profile. ============================================================ */ function RosterScreen({ ctx }) { const Y = window.YCC; const { toast } = ctx; const db = window.YCCdb; const ready = !!(db && db.isConfigured && db.isConfigured()); const [rows, setRows] = useState(null); // null = loading const [edits, setEdits] = useState({}); // id -> {handicap_index, ghin, tee} const [savingId, setSavingId] = useState(null); const [pwTarget, setPwTarget] = useState(null); // member being reset const [pwVal, setPwVal] = useState(""); const [pwState, setPwState] = useState({ busy: false, msg: "", ok: null }); function openReset(m) { setPwTarget(m); setPwVal(""); setPwState({ busy: false, msg: "", ok: null }); } function doReset() { if (pwVal.length < 6) { setPwState({ busy: false, ok: false, msg: "At least 6 characters." }); return; } setPwState({ busy: true, msg: "", ok: null }); db.resetMemberPassword(pwTarget.email, pwVal).then((res) => { if (res && res.ok) setPwState({ busy: false, ok: true, msg: "Password set. Give it to the member to sign in." }); else setPwState({ busy: false, ok: false, msg: (res && res.error) || "Failed." }); }); } function load() { if (!ready) { setRows([]); return; } setRows(null); db.getMembers().then((list) => setRows(list)).catch(() => setRows([])); } useEffect(() => { load(); }, []); function setEdit(id, patch) { setEdits((e) => ({ ...e, [id]: { ...e[id], ...patch } })); } function valOf(m, field) { const e = edits[m.id] || {}; if (field in e) return e[field]; return m[field] == null ? "" : m[field]; } function dirty(m) { return !!edits[m.id]; } function save(m, confirmOnly) { const e = edits[m.id] || {}; const hiRaw = confirmOnly ? m.handicap_index : (e.handicap_index ?? m.handicap_index); const hi = hiRaw === "" || hiRaw == null ? null : Number(hiRaw); const row = { id: m.id, email: m.email, first_name: m.first_name || "", last_name: m.last_name || "", ghin: (confirmOnly ? m.ghin : (e.ghin ?? m.ghin)) || null, handicap_index: hi, tee: (confirmOnly ? m.tee : (e.tee ?? m.tee)) || null, phone: m.phone || null, tier: m.tier || "Member", handicap_confirmed_at: new Date().toISOString(), }; setSavingId(m.id); db.upsertMember(row).then((res) => { setSavingId(null); if (res && res.error) { toast("Save failed: " + (res.error.message || res.error)); return; } toast(confirmOnly ? "Confirmed for this week" : `Updated ${m.first_name || m.email}`); setEdits((all) => { const c = { ...all }; delete c[m.id]; return c; }); setRows((list) => list.map((x) => x.id === m.id ? { ...x, ...row } : x)); }).catch(() => { setSavingId(null); toast("Save failed."); }); } function confirmedLabel(m) { if (!m.handicap_confirmed_at) return { txt: "Confirm to enable", stale: true }; const week = Y.confirmedForWeek(m.handicap_confirmed_at, Y.SAT); const d = new Date(m.handicap_confirmed_at); const days = Math.floor((Date.now() - d.getTime()) / 86400000); if (!week) return { txt: "Reconfirm this week", stale: true }; return { txt: days === 0 ? "Available · today" : days === 1 ? "Available · yesterday" : `Available · ${days}d ago`, stale: false }; } const dot = { Blue: "#2f5db0", White: "#c7c8b3", Green: "#3a7a4f", Gold: "#b08d4f", Red: "#9e3b2f" }; return (
Manager · Members

Member handicaps

Each week, review every member's Handicap Index — last week's value carries forward. Edit if it changed, then Confirm. Saved values stay in each member's profile.

{!ready && (
Connect Supabase (keys in config.js) to load and save the member roster.
)} {rows === null ? (
Loading members…
) : rows.length === 0 ? (

No members yet

Members appear here once they sign up and save their profile.

) : (
{["Member", "GHIN", "Handicap Index", "Tees", "Status", ""].map((h, i) => ( ))} {rows.map((m) => { const cl = confirmedLabel(m); const name = `${m.first_name || ""} ${m.last_name || ""}`.trim() || m.email; return ( ); })}
{h}
s[0]).join("").slice(0, 2).toUpperCase()} size={30} />
{name}
{m.email}
setEdit(m.id, { ghin: e.target.value.replace(/[^0-9]/g, "").slice(0, 8) })} style={{ width: 110, padding: "7px 10px", fontFamily: "ui-monospace, monospace" }} placeholder="—" /> setEdit(m.id, { handicap_index: e.target.value.replace(/[^0-9.]/g, "").slice(0, 4) })} style={{ width: 76, padding: "7px 10px", fontWeight: 700 }} placeholder="—" /> {cl.txt} {dirty(m) ? : }
)} {pwTarget && ( setPwTarget(null)}> setPwTarget(null)} />

Set a temporary password for {`${pwTarget.first_name || ""} ${pwTarget.last_name || ""}`.trim() || pwTarget.email}, then share it with them. They can change it later from their profile.

setPwVal(e.target.value)} placeholder="At least 6 characters" style={{ flex: 1 }} />
{pwState.msg && (
{pwState.msg}
)}
)}
); } Object.assign(window, { RosterScreen });