/* ============================================================ Final (locked) Tee Sheet — Friday 9:00 AM ET lock. A clean, screenshot-ready sheet per day for the pro shop: every DIV Tour tee time, with empty times stamped OK TO RELEASE. Includes per-day PNG download + an email composer to the Golf Shop and Head Pro. ============================================================ */ /* -------- the printable / screenshot-ready sheet for ONE day -------- */ function LockedTeeSheet({ slots, dayLabel, dateLabel, lockLabel, weekendLabel }) { const Y = window.YCC; const sorted = Y.sortByTime(slots || []); const filledTimes = sorted.filter((s) => (s.players || []).length > 0).length; const openTimes = sorted.filter((s) => (s.players || []).length === 0).length; const golfers = sorted.reduce((n, s) => n + (s.players || []).length, 0); return (
{/* header */}
YCC
Yardley Country Club
DIV Tour · Final Tee Sheet
{dayLabel}
{dateLabel}
Locked {lockLabel ? "· " + lockLabel : "· Fri 9:00 AM ET"}
{/* tee times */}
{sorted.length === 0 && (
No DIV Tour group tee times this day.
)} {sorted.map((s) => { const players = s.players || []; const empty = players.length === 0; return (
{s.time}
{empty ? "0 of 4" : players.length + " of 4"}
{[0, 1, 2, 3].map((i) => { const p = players[i]; if (!p) { return
Open
; } return (
{p.initials}
{p.name}{p.guest ? " *" : ""} HI {p.hi ?? "—"}
); })}
{empty && (
OK to release
)}
); })}
{/* footer */}
{sorted.length} tee times {golfers} golfers {openTimes > 0 && {openTimes} OK to release}
{weekendLabel ? "Weekend of " + weekendLabel : ""} · * = guest
); } /* -------- plain-text body for the pro-shop email -------- */ function proShopEmailBody(Y, days, weekendLabel) { const lines = []; lines.push("Golf Shop and Head Pro,"); lines.push(""); lines.push("Below is our group for the weekend:"); days.forEach((d) => { lines.push(""); lines.push("=================================================="); lines.push(`${d.dayLabel.toUpperCase()} · ${d.dateLabel}`); lines.push("DIV Tour — FINAL tee sheet (locked Fri 9:00 AM ET)"); lines.push("=================================================="); const sorted = Y.sortByTime(d.slots || []); if (!sorted.length) { lines.push(" (no DIV Tour group tee times this day)"); return; } sorted.forEach((s) => { const players = s.players || []; if (!players.length) { lines.push(`${s.time} — *** OK TO RELEASE (no members) ***`); return; } lines.push(`${s.time}`); players.forEach((p, i) => lines.push(` ${i + 1}. ${p.name}${p.guest ? " (guest)" : ""} — HI ${p.hi ?? "—"}${p.ghin ? " · GHIN " + p.ghin : ""}`)); }); }); lines.push(""); lines.push("Please release any tee time marked OK TO RELEASE back to the membership."); lines.push(""); lines.push("Thank you,"); lines.push("DIV Tour"); return lines.join("\n"); } /* -------- download one day's sheet as a PNG (screenshot) -------- */ function downloadSheetPng(nodeId, filename, toast) { const node = document.getElementById(nodeId); if (!node) { if (toast) toast("Tee sheet not ready yet."); return; } const lib = window.htmlToImage; if (!lib || !lib.toPng) { if (toast) toast("Image export unavailable — use your browser screenshot."); return; } if (toast) toast("Building image…"); lib.toPng(node, { pixelRatio: 2, backgroundColor: "#ffffff", cacheBust: true }) .then((dataUrl) => { const a = document.createElement("a"); a.download = filename; a.href = dataUrl; document.body.appendChild(a); a.click(); a.remove(); if (toast) toast("Saved " + filename); }) .catch(() => { if (toast) toast("Couldn't build the image — use your browser screenshot."); }); } /* -------- manager panel: previews + PNG + email actions -------- */ function FinalTeeSheetPanel({ ctx }) { const Y = window.YCC; const { tour, phase, entry, toast } = ctx; const contacts = Y.CLUB_CONTACTS || []; const proShopEmails = contacts.map((c) => c.email); const fmtET = (d) => d ? d.toLocaleString("en-US", { weekday: "short", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }) : ""; const lockLabel = entry && entry.fri ? "Fri 9:00 AM ET" : "Fri 9:00 AM ET"; const days = [ { key: "sat", dayLabel: "Saturday", iso: tour.satISO }, { key: "sun", dayLabel: "Sunday", iso: tour.sunISO }, ].map((d) => ({ ...d, slots: tour.slots[d.iso] || [], dateLabel: Y.fmtDate(new Date(d.iso + "T00:00:00")) })); const subject = `DIV Tour — Final Tee Sheet · ${tour.weekendLabel}`; const body = proShopEmailBody(Y, days, tour.weekendLabel); // Which mail client the action buttons hand off to. Persisted per device. const [mailVia, setMailVia] = React.useState(() => { try { return localStorage.getItem("ycc_mail_via") || "default"; } catch (e) { return "default"; } }); function chooseMailVia(k) { setMailVia(k); try { localStorage.setItem("ycc_mail_via", k); } catch (e) {} } function emailTo(list, who) { if (!list.length) { toast && toast("No recipient configured."); return; } const to = list.join(","); let url, newTab = false; if (mailVia === "gmail") { url = `https://mail.google.com/mail/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; newTab = true; } else if (mailVia === "outlook") { url = `https://outlook.office.com/mail/deeplink/compose?to=${encodeURIComponent(to)}&subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; newTab = true; } else { url = `mailto:${encodeURIComponent(to)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; } try { if (newTab) window.open(url, "_blank", "noopener"); else window.location.href = url; } catch (e) {} const dest = mailVia === "gmail" ? "Gmail" : mailVia === "outlook" ? "Outlook on the web" : "your default mail app"; toast && toast("Opening " + dest + " for " + who + "…"); } const locked = phase === "locked"; return (

Final tee sheet · Pro shop

{locked ? <>Tee sheets are locked — send the final groups to the Golf Shop & Head Pro. : <>At the Friday 9:00 AM ET lock, send the final groups to the Golf Shop & Head Pro. Empty times are stamped OK to release.}
{/* mail-client preference */}
Open emails in:
{[["default", "Default mail app"], ["gmail", "Gmail"], ["outlook", "Outlook web"]].map(([k, lbl], i) => ( ))}
{/* actions */}
The email opens pre-addressed to {contacts.map((c) => c.name).join(" & ") || "the pro shop"} with the greeting and both days' groups. Attach the day images above so they have a screenshot of each sheet.
{/* live previews (also the capture targets for the PNG download) */}
{days.map((d) => (
))}
); } Object.assign(window, { LockedTeeSheet, FinalTeeSheetPanel, proShopEmailBody, downloadSheetPng });