/* ============================================================ 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 (
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.
config.js) to load and save the member roster.
Members appear here once they sign up and save their profile.
| {h} | ))}|||||
|---|---|---|---|---|---|
|
{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) ? : } |
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.