Personal poker tracker: Go + React + PostgreSQL PWA for tracking home sessions and poker trips with AI-powered schedule research. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
387 lines
28 KiB
JavaScript
387 lines
28 KiB
JavaScript
import { useState, useEffect, useMemo } from "react";
|
||
|
||
const C = {
|
||
base: "#1e1e2e", mantle: "#181825", crust: "#11111b",
|
||
surface0: "#313244", surface1: "#45475a", surface2: "#585b70",
|
||
overlay0: "#6c7086", overlay1: "#7f849c", overlay2: "#9399b2",
|
||
subtext0: "#a6adc8", subtext1: "#bac2de", text: "#cdd6f4",
|
||
lavender: "#b4befe", blue: "#89b4fa", sapphire: "#74c7ec",
|
||
sky: "#89dceb", teal: "#94e2d5", green: "#a6e3a1",
|
||
yellow: "#f9e2af", peach: "#fab387", maroon: "#eba0ac",
|
||
red: "#f38ba8", mauve: "#cba6f7", pink: "#f5c2e7",
|
||
flamingo: "#f2cdcd", rosewater: "#f5e0dc",
|
||
};
|
||
|
||
const PHP_TO_USD = 0.0175;
|
||
const PHP_TO_DKK = 0.12;
|
||
|
||
const VENUES = {
|
||
okada: { name: "Okada Manila (PokerStars LIVE)", short: "Okada", color: C.blue, area: "Entertainment City", type: "tournament", note: "Main series venue — Okada Manila Millions" },
|
||
metro: { name: "Metro Card Club", short: "Metro CC", color: C.teal, area: "Ortigas Center, Pasig", type: "tournament", note: "35 tables, amazing food nearby, live music area. Early bird promo: +20% chips if registered before 2PM, +10% before end of lvl 1. 5% service charge on prize pool." },
|
||
letsin: { name: "Let's All In Poker Club", short: "Let's All In", color: C.peach, area: "Macapagal Blvd, Pasay", type: "both", note: "Daily tournaments ~6PM, ₱250+ buy-in. Cash from 10/20. PAGCOR licensed." },
|
||
masters: { name: "Masters Poker Club", short: "Masters", color: C.flamingo, area: "J. Bocobo St, Malate", type: "cash", note: "Cash games from late afternoon into early hours. Malate = great nightlife district. Occasional small tourneys." },
|
||
dynasty: { name: "Dynasty Poker Club", short: "Dynasty", color: C.pink, area: "Malate, Manila", type: "cash", note: "Cash games from late afternoon. Same Malate area as Masters." },
|
||
solaire: { name: "Solaire Resort & Casino", short: "Solaire", color: C.mauve, area: "Entertainment City", type: "cash", note: "24hr cash room (Poker King Club). Stakes 50/100 to 500/1000+ PHP. High-stakes action. Near Okada." },
|
||
cod: { name: "City of Dreams Manila", short: "CoD Manila", color: C.yellow, area: "Entertainment City", type: "cash", note: "APT venue historically. Cash games may run — verify on arrival. Poker room status unclear in 2026." },
|
||
newport: { name: "Newport World Resorts", short: "Newport", color: C.sapphire, area: "Pasay (across NAIA T3)", type: "cash", note: "24/7 cash games, 50/100 and 100/200 PHP. Literally across from the airport." },
|
||
nineD: { name: "9D Poker Club", short: "9D PC", color: C.sky, area: "Aseana Business Park, Parañaque", type: "both", note: "Luxury club near Entertainment City. Opens 2PM–6AM daily. Cash 50/100–200+. Daily tournaments with GTDs (check FB for schedule). Reopened Jan 2026. Bad Beat Jackpot running. Ph: +63 917 184 9417" },
|
||
prime: { name: "Prime Poker Club", short: "Prime", color: C.rosewater, area: "Macapagal Blvd, Pasay", type: "cash", note: "Cash games. HK SunPlaza location. Ph: +63 917 722 8667" },
|
||
twoace: { name: "2Ace Poker Manila", short: "2Ace", color: C.overlay2, area: "Manila", type: "both", note: "Community-focused club with lower rakes. Local tournaments and cash games. Check socials for schedule." },
|
||
};
|
||
|
||
const DAYS = [
|
||
{ date: "2026-05-04", label: "Monday, May 4", short: "Mon 4", dow: "Monday" },
|
||
{ date: "2026-05-05", label: "Tuesday, May 5", short: "Tue 5", dow: "Tuesday" },
|
||
{ date: "2026-05-06", label: "Wednesday, May 6", short: "Wed 6", dow: "Wednesday" },
|
||
{ date: "2026-05-07", label: "Thursday, May 7", short: "Thu 7", dow: "Thursday" },
|
||
{ date: "2026-05-08", label: "Friday, May 8", short: "Fri 8", dow: "Friday" },
|
||
{ date: "2026-05-09", label: "Saturday, May 9", short: "Sat 9", dow: "Saturday" },
|
||
{ date: "2026-05-10", label: "Sunday, May 10", short: "Sun 10", dow: "Sunday" },
|
||
];
|
||
|
||
const METRO_DAILY = {
|
||
Monday: { buyin: 750, guarantee: 50000, chips: 8000, fee: "700+50" },
|
||
Tuesday: { buyin: 1100, guarantee: 75000, chips: 10000, fee: "1,000+100" },
|
||
Wednesday: { buyin: 1500, guarantee: 100000, chips: 10000, fee: "1,400+100" },
|
||
Thursday: { buyin: 1750, guarantee: 120000, chips: 10000, fee: "1,600+150" },
|
||
Friday: { buyin: 1500, guarantee: 100000, chips: 10000, fee: "1,400+100" },
|
||
Saturday: { buyin: 1750, guarantee: 120000, chips: 10000, fee: "1,600+150" },
|
||
Sunday: { buyin: 1750, guarantee: 120000, chips: 10000, fee: "1,600+150" },
|
||
};
|
||
|
||
function buildTournaments() {
|
||
const ts = [];
|
||
let id = 0;
|
||
const nid = () => `t${id++}`;
|
||
const ok = (date, time, event, name, buyin, guarantee, format, notes, md, mdInfo) =>
|
||
ts.push({ id: nid(), date, time, venue: "okada", event, name, buyin, guarantee, format, notes, multiDay: !!md, multiDayInfo: mdInfo || null });
|
||
|
||
// MAY 4 Mon
|
||
ok("2026-05-04","12:00","#21","Ultra Stack",10000,780000,"NLH","₱780K GTD",false);
|
||
ok("2026-05-04","17:00","#22","Deepstack Turbo",8000,null,"NLH","Turbo",false);
|
||
ok("2026-05-04","20:00","#23","Okada Millions Qualifier",1900,null,"NLH","WYS at 25K Chips",false);
|
||
ok("2026-05-04","21:00","#24","Win the Button Hyper",4000,null,"NLH","Hyper Turbo",false);
|
||
// MAY 5 Tue
|
||
ok("2026-05-05","12:00","#25","Knockout [₱5K Bounty]",10000,780000,"NLH PKO","₱780K GTD · ₱5K bounty per KO",false);
|
||
ok("2026-05-05","17:00","#26","6 Handed Turbo",8000,null,"NLH","6-Max Turbo",false);
|
||
ok("2026-05-05","20:00","#27","Millions Qualifier",1900,null,"NLH","WYS at 25K Chips",false);
|
||
ok("2026-05-05","21:00","#28","Run it Twice Hyper",4000,null,"NLH","Hyper Turbo",false);
|
||
// MAY 6 Wed
|
||
ok("2026-05-06","12:00","#29A","Okada Millions Flt A ★",8000,6000000,"NLH","₱6M GTD — MAIN EVENT Flight A",true,"Day 2: May 9 · Final: May 10");
|
||
ok("2026-05-06","15:00","#30","NLH Freezeout",10000,null,"NLH","Freezeout — no re-entry",false);
|
||
ok("2026-05-06","17:00","#29B","Okada Millions Flt B ★",8000,6000000,"NLH","₱6M GTD — Flt B [20min lvls]",true,"Day 2: May 9 · Final: May 10");
|
||
ok("2026-05-06","19:00","#31","Megastack Turbo",5000,null,"NLH","Turbo",false);
|
||
ok("2026-05-06","20:00","#32","Millions Qualifier",1900,null,"NLH","WYS at 25K Chips",false);
|
||
// MAY 7 Thu
|
||
ok("2026-05-07","12:00","#29C","Okada Millions Flt C ★",8000,6000000,"NLH","₱6M GTD — Flight C",true,"Day 2: May 9 · Final: May 10");
|
||
ok("2026-05-07","15:00","#33","Shot Clock",10000,null,"NLH","Shot Clock format",false);
|
||
ok("2026-05-07","17:00","#29D","Okada Millions Flt D ★",8000,6000000,"NLH","₱6M GTD — Flt D [20min lvls]",true,"Day 2: May 9 · Final: May 10");
|
||
ok("2026-05-07","19:00","#34","KO Turbo [₱1K Bounty]",5000,null,"NLH PKO","₱1K Bounty Turbo",false);
|
||
ok("2026-05-07","20:00","#35","Millions Qualifier",1900,null,"NLH","WYS at 25K Chips",false);
|
||
// MAY 8 Fri
|
||
ok("2026-05-08","12:00","#29E","Okada Millions Flt E ★",8000,6000000,"NLH","₱6M GTD — Flight E",true,"Day 2: May 9 · Final: May 10");
|
||
ok("2026-05-08","15:00","#36A","Mystery Bounty Flt A",15000,2000000,"NLH MB","₱2M GTD — Mystery Bounty",true,"Day 2 Final: May 10");
|
||
ok("2026-05-08","15:00","#37","Millions Qualifier",1900,null,"NLH","WYS at 25K Chips",false);
|
||
ok("2026-05-08","17:00","#29F","Okada Millions Flt F ★",8000,6000000,"NLH","₱6M GTD — Flt F [20min]",true,"Day 2: May 9 · Final: May 10");
|
||
ok("2026-05-08","19:00","#38","SuperStack Turbo FO",5000,null,"NLH","Turbo Freezeout",false);
|
||
ok("2026-05-08","21:00","#29G","Okada Millions Flt G ★",8000,6000000,"NLH","₱6M GTD — Turbo Flight G",true,"Day 2: May 9 · Final: May 10");
|
||
ok("2026-05-08","22:00","#39","Mystery Bounty Qual",3000,null,"NLH","WYS at 30K Chips",false);
|
||
// MAY 9 Sat
|
||
ok("2026-05-09","12:00","#36B","Mystery Bounty Flt B",15000,2000000,"NLH MB","₱2M GTD",true,"Day 2 Final: May 10");
|
||
ok("2026-05-09","12:00","#40","Superstack Freezeout",8000,null,"NLH","Freezeout",false);
|
||
ok("2026-05-09","13:00","#29 D2","Millions — Day 2",0,6000000,"NLH","Day 2 — qualified only",true,"Final 9: May 10");
|
||
ok("2026-05-09","15:00","#41","Pot Limit Omaha",8000,null,"PLO","",false);
|
||
ok("2026-05-09","15:00","#42","Mystery Bounty Qual",3000,null,"NLH","WYS at 30K",false);
|
||
ok("2026-05-09","17:00","#36C","Mystery Bounty Flt C",15000,2000000,"NLH MB","₱2M GTD [20/15min]",true,"Day 2 Final: May 10");
|
||
ok("2026-05-09","20:00","#43","Swap Hold'em Turbo",5000,null,"Swap","Turbo",false);
|
||
ok("2026-05-09","21:00","#44","NLH 10/10/10",10000,null,"NLH","10/10/10 format",false);
|
||
// MAY 10 Sun
|
||
ok("2026-05-10","12:00","#45","Survivor KO [₱25K!]",12500,1000000,"NLH PKO","₱1M GTD · ₱25K bounty!",false);
|
||
ok("2026-05-10","12:00","#36 Fin","Mystery Bounty Final",0,2000000,"NLH MB","Day 2 Final — qualified only",true,"FINAL DAY");
|
||
ok("2026-05-10","13:00","#29 Fin","Millions — Final 9",0,6000000,"NLH","Final Table — qualified only",true,"FINAL DAY");
|
||
ok("2026-05-10","17:00","#46","6 Handed Turbo",8000,null,"NLH","6-Max Turbo",false);
|
||
ok("2026-05-10","19:00","#47","Last Chance Super Hyper",5000,null,"NLH","LAST EVENT of the series!",false);
|
||
|
||
// Metro Card Club dailies
|
||
DAYS.forEach(d => {
|
||
const m = METRO_DAILY[d.dow];
|
||
ts.push({ id: nid(), date: d.date, time: "15:00", venue: "metro", event: "Daily", name: `${d.dow} ${(m.guarantee/1000)}K GTD`, buyin: m.buyin, guarantee: m.guarantee, format: "NLH", notes: `${m.fee} PHP · ${m.chips.toLocaleString()} chips · 20min blinds · Late reg thru lvl 9 · Early bird: +20% chips before 2PM`, multiDay: false, multiDayInfo: null });
|
||
});
|
||
|
||
// Let's All In dailies
|
||
DAYS.forEach(d => {
|
||
ts.push({ id: nid(), date: d.date, time: "18:00", venue: "letsin", event: "Daily", name: "Evening Tournament", buyin: 500, guarantee: null, format: "NLH", notes: "~₱250-₱500 range (varies day-to-day). Cash games also from 10/20 PHP.", multiDay: false, multiDayInfo: null });
|
||
});
|
||
|
||
// 9D Poker Club dailies (schedule varies — check FB)
|
||
DAYS.forEach(d => {
|
||
ts.push({ id: nid(), date: d.date, time: "17:00", venue: "nineD", event: "Daily", name: "Daily GTD Tournament", buyin: 2000, guarantee: 50000, format: "NLH", notes: "~₱1K-₱3K range (varies). GTDs from ₱50K–₱500K+ depending on day. Check 9D FB page for exact schedule. Opens 2PM.", multiDay: false, multiDayInfo: null });
|
||
});
|
||
|
||
return ts;
|
||
}
|
||
|
||
const ALL_T = buildTournaments();
|
||
|
||
const STATUS = {
|
||
upcoming: { label: "Upcoming", color: C.overlay1, icon: "⏳" },
|
||
registered: { label: "Registered", color: C.yellow, icon: "🎫" },
|
||
playing: { label: "Playing", color: C.green, icon: "🃏" },
|
||
busted: { label: "Busted", color: C.red, icon: "💀" },
|
||
cashed: { label: "Cashed!", color: C.teal, icon: "💰" },
|
||
day2: { label: "Made Day 2", color: C.mauve, icon: "🌟" },
|
||
skipped: { label: "Skipped", color: C.surface2, icon: "⏭️" },
|
||
};
|
||
|
||
const SK = "manila-poker-v3";
|
||
const load = () => { try { return JSON.parse(localStorage.getItem(SK)); } catch { return null; } };
|
||
const save = s => { try { localStorage.setItem(SK, JSON.stringify(s)); } catch {} };
|
||
const cv = (php, cur) => { if (php == null) return "—"; if (cur==="USD") return `$${Math.round(php*PHP_TO_USD).toLocaleString()}`; if (cur==="DKK") return `${Math.round(php*PHP_TO_DKK).toLocaleString()} kr`; return `₱${php.toLocaleString()}`; };
|
||
|
||
export default function App() {
|
||
const sv = load();
|
||
const [day, setDay] = useState("2026-05-04");
|
||
const [sts, setSts] = useState(sv?.sts || {});
|
||
const [prizes, setPrizes] = useState(sv?.prizes || {});
|
||
const [places, setPlaces] = useState(sv?.places || {});
|
||
const [reents, setReents] = useState(sv?.reents || {});
|
||
const [editId, setEditId] = useState(null);
|
||
const [showBudget, setShowBudget] = useState(false);
|
||
const [cur, setCur] = useState("PHP");
|
||
const [vf, setVf] = useState("all");
|
||
const [showCash, setShowCash] = useState(false);
|
||
const [dimOthers, setDimOthers] = useState(true);
|
||
|
||
useEffect(() => { save({ sts, prizes, places, reents }); }, [sts, prizes, places, reents]);
|
||
|
||
const gs = id => sts[id] || "upcoming";
|
||
const ss = (id, s) => { setSts(p => ({...p,[id]:s})); if(s==="cashed") setEditId(id); else setEditId(null); };
|
||
|
||
const dayTs = useMemo(() => {
|
||
let t = ALL_T.filter(t => t.date === day);
|
||
if (vf !== "all") t = t.filter(t => t.venue === vf);
|
||
return t.sort((a,b) => a.time.localeCompare(b.time) || a.venue.localeCompare(b.venue));
|
||
}, [day, vf]);
|
||
|
||
const hasActive = dayTs.some(t => ["playing","registered"].includes(gs(t.id)));
|
||
|
||
const budget = useMemo(() => {
|
||
let sp=0, wn=0, cnt=0, cs=0;
|
||
ALL_T.forEach(t => {
|
||
const s = gs(t.id);
|
||
if (["registered","playing","busted","cashed","day2"].includes(s)) { sp += t.buyin*(1+(reents[t.id]||0)); cnt++; }
|
||
if (s==="cashed" && prizes[t.id]) { wn += prizes[t.id]; cs++; }
|
||
});
|
||
return { sp, wn, net: wn-sp, cnt, cs };
|
||
}, [sts, prizes, reents]);
|
||
|
||
const dStats = date => {
|
||
const t = ALL_T.filter(t => t.date === date);
|
||
const p = t.filter(t => !["upcoming","skipped"].includes(gs(t.id)));
|
||
const w = t.filter(t => gs(t.id)==="cashed");
|
||
return { tot: t.length, pl: p.length, wn: w.length };
|
||
};
|
||
|
||
const $cv = php => cv(php, cur);
|
||
|
||
return (
|
||
<div style={{ fontFamily: "'JetBrains Mono','Fira Code','SF Mono',monospace", background: `linear-gradient(170deg,${C.crust},${C.base} 40%,${C.mantle})`, color: C.text, minHeight: "100vh" }}>
|
||
|
||
{/* HEADER */}
|
||
<div style={{ background: `linear-gradient(135deg,${C.mantle},${C.crust}ee)`, borderBottom: `1px solid ${C.surface0}`, padding: "14px 16px 10px", position: "sticky", top: 0, zIndex: 100, backdropFilter: "blur(16px)" }}>
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", flexWrap: "wrap", gap: 8 }}>
|
||
<div>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||
<span style={{ fontSize: 22 }}>🃏</span>
|
||
<h1 style={{ margin: 0, fontSize: 17, fontWeight: 700, background: `linear-gradient(135deg,${C.blue},${C.mauve})`, WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent" }}>Manila Poker Trip</h1>
|
||
</div>
|
||
<p style={{ margin: "2px 0 0", fontSize: 9, color: C.overlay1, letterSpacing: "0.5px" }}>MAY 4–10 · OKADA MILLIONS + METRO CC + LOCAL ROOMS</p>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 5, alignItems: "center" }}>
|
||
<button onClick={() => setShowBudget(!showBudget)} style={{ background: showBudget ? C.surface1 : C.surface0, border: `1px solid ${C.surface1}`, color: C.text, padding: "4px 8px", borderRadius: 5, fontSize: 10, cursor: "pointer", fontFamily: "inherit" }}>💰</button>
|
||
<select value={cur} onChange={e => setCur(e.target.value)} style={{ background: C.surface0, border: `1px solid ${C.surface1}`, color: C.text, padding: "4px 5px", borderRadius: 5, fontSize: 10, cursor: "pointer", fontFamily: "inherit" }}>
|
||
<option value="PHP">₱</option><option value="USD">$</option><option value="DKK">kr</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
{showBudget && (
|
||
<div style={{ marginTop: 8, padding: 10, background: C.surface0, borderRadius: 8, display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(100px,1fr))", gap: 6 }}>
|
||
<SB l="Buy-ins" v={$cv(budget.sp)} s={`${budget.cnt} events`} c={C.red} />
|
||
<SB l="Won" v={$cv(budget.wn)} s={`${budget.cs} cashes`} c={C.green} />
|
||
<SB l="Net" v={(budget.net>=0?"+":"-")+$cv(Math.abs(budget.net))} s={budget.net>=0?"PROFIT":"LOSS"} c={budget.net>=0?C.teal:C.maroon} />
|
||
<SB l="ROI" v={budget.sp>0?`${Math.round(((budget.wn-budget.sp)/budget.sp)*100)}%`:"—"} s="" c={C.lavender} />
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* DAY TABS */}
|
||
<div style={{ display: "flex", gap: 3, padding: "8px 10px", overflowX: "auto" }}>
|
||
{DAYS.map(d => {
|
||
const s = dStats(d.date); const a = d.date===day;
|
||
return <button key={d.date} onClick={() => setDay(d.date)} style={{ flex: "0 0 auto", padding: "5px 10px", borderRadius: 7, border: a?`2px solid ${C.blue}`:`1px solid ${C.surface1}50`, background: a?`${C.blue}18`:"transparent", color: a?C.blue:C.subtext1, cursor: "pointer", fontFamily: "inherit", fontSize: 10, fontWeight: a?700:500, textAlign: "center" }}>
|
||
<div>{d.short}</div>
|
||
{s.pl > 0 && <div style={{ fontSize: 8, marginTop: 1, color: s.wn>0?C.green:C.overlay0 }}>{s.pl}/{s.tot}{s.wn>0?` 💰${s.wn}`:""}</div>}
|
||
</button>;
|
||
})}
|
||
</div>
|
||
|
||
{/* FILTERS */}
|
||
<div style={{ padding: "3px 10px", display: "flex", gap: 3, overflowX: "auto", flexWrap: "wrap" }}>
|
||
{[{k:"all",l:"All",c:C.text},{k:"okada",l:"Okada",c:VENUES.okada.color},{k:"metro",l:"Metro CC",c:VENUES.metro.color},{k:"letsin",l:"Let's All In",c:VENUES.letsin.color},{k:"nineD",l:"9D PC",c:VENUES.nineD.color}].map(f =>
|
||
<button key={f.k} onClick={() => { setVf(f.k); setShowCash(false); }} style={{ padding: "2px 7px", borderRadius: 4, border: `1px solid ${vf===f.k?f.c:C.surface1}40`, background: vf===f.k?`${f.c}20`:"transparent", color: vf===f.k?f.c:C.overlay1, cursor: "pointer", fontFamily: "inherit", fontSize: 9, fontWeight: vf===f.k?600:400, whiteSpace: "nowrap" }}>{f.l}</button>
|
||
)}
|
||
<button onClick={() => setShowCash(!showCash)} style={{ padding: "2px 7px", borderRadius: 4, border: `1px solid ${showCash?C.peach:C.surface1}40`, background: showCash?`${C.peach}20`:"transparent", color: showCash?C.peach:C.overlay1, cursor: "pointer", fontFamily: "inherit", fontSize: 9, whiteSpace: "nowrap" }}>
|
||
🎰 More Venues {showCash?"▴":"▾"}
|
||
</button>
|
||
</div>
|
||
|
||
{/* OTHER VENUES PANEL */}
|
||
{showCash && (
|
||
<div style={{ margin: "6px 10px", padding: 10, background: C.surface0, borderRadius: 8, fontSize: 10 }}>
|
||
<div style={{ fontWeight: 700, marginBottom: 6, color: C.subtext1, fontSize: 11 }}>Other Poker Venues (cash + walk-in tourneys)</div>
|
||
{["nineD","masters","dynasty","solaire","cod","newport","prime","twoace"].map(k => {
|
||
const v = VENUES[k];
|
||
return <div key={k} style={{ marginBottom: 6, padding: "6px 8px", background: `${v.color}08`, borderRadius: 6, borderLeft: `3px solid ${v.color}` }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
||
<span style={{ fontWeight: 600, color: v.color, fontSize: 11 }}>{v.name}</span>
|
||
{v.type === "both" && <span style={{ fontSize: 8, padding: "1px 4px", borderRadius: 3, background: `${C.green}20`, color: C.green, fontWeight: 600 }}>Tourneys</span>}
|
||
{v.type === "cash" && <span style={{ fontSize: 8, padding: "1px 4px", borderRadius: 3, background: `${C.overlay0}20`, color: C.overlay0, fontWeight: 600 }}>Cash only</span>}
|
||
</div>
|
||
<div style={{ color: C.overlay1, marginTop: 1 }}>{v.area}</div>
|
||
<div style={{ color: C.subtext0, marginTop: 1, lineHeight: 1.3 }}>{v.note}</div>
|
||
</div>;
|
||
})}
|
||
</div>
|
||
)}
|
||
|
||
{/* TOURNAMENTS */}
|
||
<div style={{ padding: "6px 10px 90px" }}>
|
||
{hasActive && (
|
||
<div style={{ padding: "5px 8px", background: `${C.green}10`, border: `1px solid ${C.green}30`, borderRadius: 7, marginBottom: 8, fontSize: 10, color: C.green, display: "flex", alignItems: "center", gap: 5 }}>
|
||
<span style={{ fontSize: 13 }}>🎯</span> Active session!
|
||
<label style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 3, color: C.overlay1, cursor: "pointer", fontSize: 9 }}>
|
||
<input type="checkbox" checked={dimOthers} onChange={e => setDimOthers(e.target.checked)} style={{ accentColor: C.green, width: 12, height: 12 }} /> Dim others
|
||
</label>
|
||
</div>
|
||
)}
|
||
|
||
{dayTs.length === 0 ? <div style={{ textAlign: "center", padding: 30, color: C.overlay0, fontSize: 12 }}>No tournaments for this day/filter.</div> :
|
||
dayTs.map(t => <TC key={t.id} t={t} status={gs(t.id)} ss={s => ss(t.id,s)} editId={editId} setEditId={setEditId} prize={prizes[t.id]} setPrize={v => setPrizes(p => ({...p,[t.id]:v}))} place={places[t.id]} setPlace={v => setPlaces(p => ({...p,[t.id]:v}))} re={reents[t.id]||0} setRe={v => setReents(p => ({...p,[t.id]:v}))} $cv={$cv} cur={cur} dim={hasActive && dimOthers} />)
|
||
}
|
||
</div>
|
||
|
||
{/* FOOTER */}
|
||
<div style={{ position: "fixed", bottom: 0, left: 0, right: 0, background: `${C.crust}f0`, backdropFilter: "blur(12px)", borderTop: `1px solid ${C.surface0}`, padding: "5px 10px", fontSize: 8, color: C.overlay0, display: "flex", justifyContent: "space-between" }}>
|
||
<span>Okada Manila Millions · Apr 29–May 10</span>
|
||
<span>3% staffing (Okada) · 5% svc (Metro) · All PHP</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function SB({ l, v, s, c }) {
|
||
return <div style={{ padding: "6px 8px", background: `${c}08`, borderRadius: 5, border: `1px solid ${c}20` }}>
|
||
<div style={{ fontSize: 8, color: C.overlay1, textTransform: "uppercase", letterSpacing: 0.7 }}>{l}</div>
|
||
<div style={{ fontSize: 14, fontWeight: 700, color: c }}>{v}</div>
|
||
{s && <div style={{ fontSize: 8, color: C.overlay0 }}>{s}</div>}
|
||
</div>;
|
||
}
|
||
|
||
function TC({ t, status, ss, editId, setEditId, prize, setPrize, place, setPlace, re, setRe, $cv, cur, dim }) {
|
||
const v = VENUES[t.venue];
|
||
const cfg = STATUS[status];
|
||
const isAct = ["playing","registered","day2"].includes(status);
|
||
const isDone = ["busted","cashed","skipped"].includes(status);
|
||
const dimmed = dim && !isAct && status==="upcoming";
|
||
const isEd = editId === t.id;
|
||
const free = t.buyin === 0;
|
||
|
||
return (
|
||
<div style={{ marginBottom: 5, borderRadius: 8, border: `1px solid ${isAct?cfg.color:C.surface0}${isAct?"60":"80"}`, background: isAct?`linear-gradient(135deg,${cfg.color}10,${C.surface0}60)`:`${C.surface0}60`, overflow: "hidden", opacity: isDone&&status!=="cashed"?0.45:dimmed?0.35:1, transition: "all 0.2s" }}>
|
||
<div style={{ height: 2, background: v.color, opacity: 0.5 }} />
|
||
<div style={{ padding: "8px 10px" }}>
|
||
{/* Row 1 */}
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 3, flexWrap: "wrap", gap: 3 }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 5, flexWrap: "wrap" }}>
|
||
<span style={{ fontWeight: 700, fontSize: 12, color: C.blue, fontVariantNumeric: "tabular-nums" }}>{t.time}</span>
|
||
<Bdg text={v.short} color={v.color} />
|
||
{t.multiDay && <Bdg text="Multi-Day" color={C.mauve} />}
|
||
<Bdg text={t.format} color={C.overlay1} bg={`${C.surface1}60`} />
|
||
</div>
|
||
<span style={{ fontSize: 9, color: cfg.color, fontWeight: 600 }}>{cfg.icon} {cfg.label}</span>
|
||
</div>
|
||
{/* Row 2 */}
|
||
<div style={{ marginBottom: 3 }}>
|
||
<span style={{ color: C.overlay0, fontSize: 9, marginRight: 4 }}>{t.event}</span>
|
||
<span style={{ fontWeight: 600, fontSize: 12, color: t.name.includes("★")?C.yellow:C.text }}>{t.name}</span>
|
||
</div>
|
||
{/* Row 3 */}
|
||
<div style={{ display: "flex", gap: 12, alignItems: "center", marginBottom: 2, flexWrap: "wrap" }}>
|
||
{!free && <div style={{ fontSize: 10 }}><span style={{ color: C.overlay0 }}>Buy-in: </span><span style={{ fontWeight: 600, color: C.yellow }}>{$cv(t.buyin)}</span>{re>0 && <span style={{ color: C.peach, fontSize: 9, marginLeft: 2 }}>(+{re}re={$cv(t.buyin*(1+re))})</span>}</div>}
|
||
{t.guarantee && <div style={{ fontSize: 10 }}><span style={{ color: C.overlay0 }}>GTD: </span><span style={{ fontWeight: 600, color: C.green }}>{$cv(t.guarantee)}</span></div>}
|
||
</div>
|
||
{t.notes && <div style={{ fontSize: 9, color: C.overlay1, marginBottom: 2, lineHeight: 1.3 }}>{t.notes}</div>}
|
||
{t.multiDay && t.multiDayInfo && <div style={{ fontSize: 9, color: C.mauve, marginBottom: 3 }}>📅 {t.multiDayInfo}</div>}
|
||
|
||
{status==="cashed" && prize>0 && (
|
||
<div style={{ padding: "5px 7px", background: `${C.teal}15`, borderRadius: 5, marginBottom: 5, display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
|
||
<div><span style={{ fontSize: 8, color: C.overlay0 }}>Prize: </span><span style={{ fontWeight: 700, color: C.teal, fontSize: 12 }}>{$cv(prize)}</span></div>
|
||
{place>0 && <div><span style={{ fontSize: 8, color: C.overlay0 }}>Finish: </span><span style={{ fontWeight: 700, color: C.lavender, fontSize: 12 }}>#{place}</span></div>}
|
||
<div style={{ marginLeft: "auto", fontSize: 9, color: prize>t.buyin*(1+(re||0))?C.green:C.red, fontWeight: 600 }}>
|
||
{prize>t.buyin*(1+(re||0))?"+":""}{$cv(prize-t.buyin*(1+(re||0)))} net
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{isEd && (
|
||
<div style={{ padding: 8, background: C.surface0, borderRadius: 5, marginBottom: 5 }}>
|
||
<div style={{ fontSize: 9, color: C.subtext0, marginBottom: 5, fontWeight: 600 }}>Record Result</div>
|
||
<div style={{ display: "flex", gap: 5, flexWrap: "wrap", alignItems: "flex-end" }}>
|
||
<div>
|
||
<label style={{ fontSize: 8, color: C.overlay0 }}>Prize (₱)</label>
|
||
<input type="number" value={prize||""} onChange={e => setPrize(parseInt(e.target.value)||0)} placeholder="0" style={{ display: "block", marginTop: 2, padding: "3px 5px", background: C.surface1, border: `1px solid ${C.surface2}`, borderRadius: 3, color: C.text, fontFamily: "inherit", fontSize: 11, width: 80 }} />
|
||
</div>
|
||
<div>
|
||
<label style={{ fontSize: 8, color: C.overlay0 }}>Finish #</label>
|
||
<input type="number" value={place||""} onChange={e => setPlace(parseInt(e.target.value)||0)} placeholder="#" style={{ display: "block", marginTop: 2, padding: "3px 5px", background: C.surface1, border: `1px solid ${C.surface2}`, borderRadius: 3, color: C.text, fontFamily: "inherit", fontSize: 11, width: 45 }} />
|
||
</div>
|
||
<button onClick={() => setEditId(null)} style={{ padding: "3px 10px", background: C.teal, border: "none", borderRadius: 3, color: C.crust, fontFamily: "inherit", fontSize: 10, fontWeight: 700, cursor: "pointer" }}>Save</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Actions */}
|
||
<div style={{ display: "flex", gap: 3, flexWrap: "wrap", marginTop: 2 }}>
|
||
{status==="upcoming" && !free && <><AB l="🎫 Register" c={C.yellow} o={() => ss("registered")} /><AB l="⏭️ Skip" c={C.surface2} o={() => ss("skipped")} /></>}
|
||
{status==="registered" && <><AB l="🃏 Playing" c={C.green} o={() => ss("playing")} /><AB l="↩️" c={C.overlay0} o={() => ss("upcoming")} /></>}
|
||
{status==="playing" && <>
|
||
<AB l="💀 Bust" c={C.red} o={() => ss("busted")} />
|
||
<AB l="💰 Cash!" c={C.teal} o={() => ss("cashed")} />
|
||
{t.multiDay && <AB l="🌟 Day 2" c={C.mauve} o={() => ss("day2")} />}
|
||
<div style={{ display: "flex", alignItems: "center", gap: 2, marginLeft: 4 }}>
|
||
<span style={{ fontSize: 8, color: C.overlay0 }}>Re:</span>
|
||
<SBtn o={() => setRe(Math.max(0,re-1))} l="−" d={re===0} />
|
||
<span style={{ fontSize: 10, fontWeight: 600, minWidth: 10, textAlign: "center" }}>{re}</span>
|
||
<SBtn o={() => setRe(re+1)} l="+" />
|
||
</div>
|
||
</>}
|
||
{status==="day2" && <><AB l="🃏 D2" c={C.green} o={() => ss("playing")} /><AB l="💀" c={C.red} o={() => ss("busted")} /><AB l="💰" c={C.teal} o={() => ss("cashed")} /></>}
|
||
{status==="cashed" && !isEd && <AB l="✏️ Edit" c={C.lavender} o={() => setEditId(t.id)} />}
|
||
{["busted","cashed","skipped"].includes(status) && <AB l="↩️ Reset" c={C.overlay0} o={() => { ss("upcoming"); setPrize(undefined); setPlace(undefined); setRe(0); }} />}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function Bdg({ text, color, bg }) {
|
||
return <span style={{ fontSize: 8, padding: "1px 4px", borderRadius: 3, background: bg||`${color}20`, color, fontWeight: 600 }}>{text}</span>;
|
||
}
|
||
|
||
function AB({ l, c, o }) {
|
||
return <button onClick={o} style={{ padding: "3px 7px", borderRadius: 4, border: `1px solid ${c}40`, background: `${c}15`, color: c, cursor: "pointer", fontFamily: "inherit", fontSize: 9, fontWeight: 600 }} onMouseEnter={e=>e.target.style.background=`${c}30`} onMouseLeave={e=>e.target.style.background=`${c}15`}>{l}</button>;
|
||
}
|
||
|
||
function SBtn({ o, l, d }) {
|
||
return <button onClick={o} style={{ width: 16, height: 16, borderRadius: 2, border: `1px solid ${C.surface2}`, background: C.surface1, color: C.text, fontSize: 11, fontWeight: 700, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "inherit", padding: 0, opacity: d?0.3:1 }}>{l}</button>;
|
||
}
|