/* ============================================================ Manager mode — DIV Tour Setup Publish & edit the weekly Saturday/Sunday tee times. ============================================================ */ function inputToLabel(v) { // "07:30" -> "7:30 AM" const [h, m] = v.split(":").map(Number); const ap = h >= 12 ? "PM" : "AM"; const hh = h % 12 === 0 ? 12 : h % 12; return `${hh}:${String(m).padStart(2, "0")} ${ap}`; } function labelToInput(label) { const mins = window.YCC.parseTime(label); const h = Math.floor(mins / 60), m = mins % 60; return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`; } // A full-width add-on toggle row (icon · name · meta · switch) used in Tour Setup. function SideToggle({ on, icon, name, meta, onClick }) { return ( ); } function ManagerDay({ day, slots, onAdd, onEdit, onRemove }) { const Y = window.YCC; const sorted = Y.sortByTime(slots); const [newTime, setNewTime] = useState(""); const [editingId, setEditingId] = useState(null); const [editVal, setEditVal] = useState(""); const claimed = sorted.reduce((n, s) => n + s.players.length, 0); const capacity = sorted.length * 4; return (

{day.long}

{sorted.length} tee times · {claimed}/{capacity} spots claimed
{sorted.length === 0 && (
No tee times yet — add the first one below.
)} {sorted.map((s, idx) => { const editing = editingId === s.id; return (
{editing ? (
setEditVal(e.target.value)} style={{ width: 150 }} />
) : ( <>
{s.time}
{[0, 1, 2, 3].map(i => { const p = s.players[i]; return ( {p ? p.initials : ""} ); })}
{s.players.length}/4
)}
); })}
Add tee time setNewTime(e.target.value)} style={{ width: 150 }} />
); } function ManagerCard({ m, onDuty, isAdmin, onToggleAvail, onSave }) { const [editing, setEditing] = useState(false); const [form, setForm] = useState({ name: m.name, email: m.email, phone: m.phone }); const blank = !m.name; function save() { onSave({ name: form.name.trim(), email: form.email.trim(), phone: form.phone.trim() }); setEditing(false); } const cardStyle = { display: "flex", flexDirection: "column", gap: 0, padding: 16, borderRadius: "var(--radius-sm)", border: "1px solid " + (onDuty ? "var(--accent)" : blank ? "var(--line-strong)" : "var(--line)"), background: onDuty ? "color-mix(in srgb,var(--accent) 8%,var(--surface))" : blank ? "var(--surface-2)" : "var(--surface)", opacity: m.available || blank ? 1 : .6, }; if (blank && !editing) { return (
{m.role} manager
Not assigned yet
{isAdmin ? ( ) : (
The administrator assigns this role.
)}
); } if (editing) { return (
{blank ? `Add ${m.role.toLowerCase()} manager` : "Edit manager"}
setForm({ ...form, name: e.target.value })} placeholder="Full name" />
setForm({ ...form, email: e.target.value })} placeholder="name@email.com" />
setForm({ ...form, phone: window.YCC.formatPhone(e.target.value) })} placeholder="(215) 555-0100" inputMode="tel" />
); } return (
{m.name}
{m.role} {m.admin && Admin} {onDuty && On duty}
{m.title}
{m.email} {m.phone && {m.phone}}
{isAdmin && }
); } function ManagerScreen({ ctx }) { const Y = window.YCC; const { tour, dayKeys, addTime, editTime, requestRemoveTime, togglePublish, activeManager, toggleManagerAvail, isAdmin, setManagerInfo, gameConfig, setGameConfig, phase, entry } = ctx; const G = window.YCC_GAMES; const { TeeSelect, Seg } = window; const FinalTeeSheetPanel = window.FinalTeeSheetPanel; const PostWeekendCard = window.PostWeekendCard; const setCfg = (patch) => setGameConfig({ ...gameConfig, ...patch }); const STAKE_LABEL = { skins: "Skin value", stableford: "Ante (each)", nassau: "Bet / match", "split-sixes": "Point value" }; const idsInPlay = Array.from(new Set(gameConfig.splitDays ? [gameConfig.gameSat || "skins", gameConfig.gameSun || "skins"] : [gameConfig.gameType || "skins"])); const anySkins = idsInPlay.includes("skins"); const stakeLabel = idsInPlay.length === 1 ? (STAKE_LABEL[idsInPlay[0]] || "Stake") : "Stake"; const fmtET = (d) => d.toLocaleString("en-US", { weekday: "long", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }); const phaseCopy = { open: { c: "var(--ok)", t: "Open to members", d: `Members can self-serve until Wed 11:59 PM ET (${fmtET(entry.wed)}).` }, managerOnly: { c: "var(--accent-deep)", t: "Manager-managed", d: `Member sign-ups closed. You can add / move / release until Fri 9:00 AM ET (${fmtET(entry.fri)}).` }, locked: { c: "var(--ink-faint)", t: "Members final · manager game-day control", d: "Member sign-ups are closed. You can still adjust groups and swap players between foursomes on game day." }, }[phase]; const activeGame = G.gameById(gameConfig.gameType || "skins"); const days = [ { iso: tour.satISO, long: Y.fmtDate(Y.SAT) }, { iso: tour.sunISO, long: Y.fmtDate(Y.SUN) }, ]; const allSlots = [...(tour.slots[tour.satISO] || []), ...(tour.slots[tour.sunISO] || [])]; const totalTimes = allSlots.length; const totalClaimed = allSlots.reduce((n, s) => n + s.players.length, 0); const totalOpen = totalTimes * 4 - totalClaimed; return (
Manager · DIV Tour

Tour setup

Publish the tee times for the weekend of {tour.weekendLabel}. Members claim open spots and their handicaps attach automatically.

{tour.published ? "Live to members" : "Draft — hidden"}
{/* sign-up window status */}
Sign-up window · {phaseCopy.t}
{phaseCopy.d}
Members close · {fmtET(entry.wed)}
Final lock · {fmtET(entry.fri)}
{/* stats */}
{/* final tee sheet → pro shop */} {FinalTeeSheetPanel && } {/* certify & post weekend results to member season stats */} {PostWeekendCard && } {/* tour managers */}

Tour managers

A backup covers sign-ups whenever the primary is away.
{tour.managers.map((m, i) => ( toggleManagerAvail(i)} onSave={(patch) => setManagerInfo(i, patch)} /> ))}
{!tour.managers.some(m => m.available) && (
No manager is currently on duty — members will see {activeManager.name} as the default contact.
)}
{/* weekend game format */}

Weekend Games

Choose this weekend's format. Members see the rules on their Weekend Games tab.
{/* scoring controls — these drive every group scorecard (moved here from Weekend Games) */}
{stakeLabel}
${gameConfig.skinValue || 0}
{anySkins && Seg && setCfg({ scoring: v })} options={[["gross", "Gross"], ["net", "Net"]]} />}
Group tees {TeeSelect && setCfg({ groupTee: v })} />} drives every card
{anySkins && (
Carryover
)}
{/* same game both days, or a different game each day */}
Schedule
{[[false, "Same game both days"], [true, "Different each day"]].map(([val, lab]) => ( ))}
{!gameConfig.splitDays ? (
This weekend's game
{G.gameById(gameConfig.gameType || "skins").name} · Sat & Sun
) : (
{[["gameSat", "Saturday", Y.shortDate(Y.SAT)], ["gameSun", "Sunday", Y.shortDate(Y.SUN)]].map(([key, label, sub]) => (
{label} {sub}
))}
)}
Handicap strokes off
{[["day", "The day's low (all groups)"], ["group", "Each group's low"]].map(([val, lab]) => { const on = (gameConfig.lowMode || "day") === val; return ( ); })}
Default: every player gets strokes vs the lowest handicap playing that day.
{/* daily add-on games — side contests layered on the main game, set per day */} {(() => { const sideCfg = gameConfig.sideGames || G.defaultSideGames(); const par3s = G.parThreeHoles(); const dayCfg = (k) => sideCfg[k] || {}; const patchDay = (k, patch) => setGameConfig({ ...gameConfig, sideGames: { ...sideCfg, [k]: { ...(sideCfg[k] || {}), ...patch } } }); const toggleSide = (k, id) => patchDay(k, { [id]: !(sideCfg[k] && sideCfg[k][id]) }); const ctpHolesOf = (k) => Array.isArray(dayCfg(k).ctpHoles) ? dayCfg(k).ctpHoles : par3s.slice(); const toggleCtpHole = (k, n) => { const cur = ctpHolesOf(k); const next = cur.includes(n) ? cur.filter((x) => x !== n) : [...cur, n].sort((a, b) => a - b); patchDay(k, { ctpHoles: next }); }; const setAllCtp = (k, all) => patchDay(k, { ctpHoles: all ? par3s.slice() : [] }); const ldHoleOf = (k) => dayCfg(k).longDriveHole || sideCfg.longDriveHole || 7; const yardsOf = (n) => { const h = Y.HOLES.find((x) => x.n === n); return h ? h.yards : ""; }; return (
Daily add-on games
Layer extra contests on top of the main game — pick them, and their holes, for each day. Each group marks the winners on their scorecard during play.
{[["sat", "Saturday", Y.shortDate(Y.SAT)], ["sun", "Sunday", Y.shortDate(Y.SUN)]].map(([key, label, sub]) => { const ctpOn = !!dayCfg(key).ctp; const ldOn = !!dayCfg(key).longDrive; const ctpHoles = ctpHolesOf(key); return (
{label} {sub}
{/* Closest to the Pin */}
"#" + n).join(" ")}` : "No holes selected") : `Par 3s · ${par3s.map((n) => "#" + n).join(" ")}`} onClick={() => toggleSide(key, "ctp")} /> {ctpOn && (
Holes in play ·
{par3s.map((n) => { const sel = ctpHoles.includes(n); return ( ); })}
)}
{/* Longest Drive */}
toggleSide(key, "longDrive")} /> {ldOn && (
Hole
)}
); })}
); })()}
Tap any game below to read its rules & scoring. The group scorecard uses the selected game's scoring & winnings.
{days.map(d => ( addTime(d.iso, label)} onEdit={(slotId, label) => editTime(d.iso, slotId, label)} onRemove={(slot) => requestRemoveTime(d.iso, slot)} /> ))}
); } Object.assign(window, { ManagerScreen, ManagerDay });