diff --git a/static/css/style.css b/static/css/style.css index ce7fbef..89126f9 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -322,6 +322,10 @@ h2 { grid-template-columns: repeat(3, minmax(0, 1fr)); } +.four-col { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + .stack-gap { display: grid; gap: 18px; @@ -944,6 +948,12 @@ td a:hover { border: 1px solid rgba(96, 165, 250, 0.22); } +.pill-front { + color: #fb923c; + background: rgba(251, 146, 60, 0.12); + border: 1px solid rgba(251, 146, 60, 0.22); +} + .pill-note { color: #cbd5e1; background: rgba(203, 213, 225, 0.08); @@ -963,6 +973,12 @@ td a:hover { text-transform: uppercase; } +.status-owes { + color: #ef4444; + background: rgba(239, 68, 68, 0.12); + border: 1px solid rgba(239, 68, 68, 0.24); +} + .inline-action-form { margin: 0; } @@ -1032,6 +1048,24 @@ td a:hover { border-color: rgba(255, 166, 77, 0.28); } +.payout-summary-grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +@media (max-width: 1100px) { + .payout-summary-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 640px) { + .payout-summary-grid { + grid-template-columns: 1fr; + } +} + @media (max-width: 980px) { .site-shell { width: min(100% - 24px, 100%); diff --git a/stats.py b/stats.py index 9ebe6c1..4f7ce14 100644 --- a/stats.py +++ b/stats.py @@ -29,17 +29,26 @@ class SessionEntry: session_date: str player_name: str buy_in_cents: int = 0 + front_cents: int = 0 cash_out_cents: int = 0 paid_cents: int = 0 notes: list[str] = field(default_factory=list) + @property + def invested_cents(self) -> int: + return self.buy_in_cents + self.front_cents + @property def net_cents(self) -> int: - return self.cash_out_cents - self.buy_in_cents + return self.cash_out_cents - self.invested_cents @property def payout_due_cents(self) -> int: - return max(self.cash_out_cents, 0) + return max(self.cash_out_cents - self.front_cents, 0) + + @property + def player_owes_cents(self) -> int: + return max(self.front_cents - self.cash_out_cents, 0) @property def payout_remaining_cents(self) -> int: @@ -47,6 +56,8 @@ class SessionEntry: @property def payout_status(self) -> str: + if self.player_owes_cents > 0: + return "owes" if self.payout_due_cents <= 0: return "none" if self.paid_cents <= 0: @@ -70,26 +81,38 @@ class SessionSummary: def total_buy_in_cents(self) -> int: return sum(entry.buy_in_cents for entry in self.entries) + @property + def total_front_cents(self) -> int: + return sum(entry.front_cents for entry in self.entries) + + @property + def total_invested_cents(self) -> int: + return sum(entry.invested_cents for entry in self.entries) + @property def total_cash_out_cents(self) -> int: return sum(entry.cash_out_cents for entry in self.entries) - @property - def total_net_cents(self) -> int: - return sum(entry.net_cents for entry in self.entries) - - @property - def total_payout_due_cents(self) -> int: - return sum(entry.payout_due_cents for entry in self.entries) - @property def total_paid_cents(self) -> int: return sum(entry.paid_cents for entry in self.entries) + @property + def total_payout_due_cents(self) -> int: + return sum(entry.payout_due_cents for entry in self.entries) + + @property + def total_player_owes_cents(self) -> int: + return sum(entry.player_owes_cents for entry in self.entries) + @property def total_remaining_cents(self) -> int: return sum(entry.payout_remaining_cents for entry in self.entries) + @property + def total_net_cents(self) -> int: + return sum(entry.net_cents for entry in self.entries) + @dataclass class PlayerStats: @@ -104,6 +127,8 @@ class PlayerStats: biggest_win_cents: int biggest_loss_cents: int total_buy_in_cents: int + total_front_cents: int + total_invested_cents: int total_cash_out_cents: int total_net_cents: int roi_pct: float @@ -200,6 +225,8 @@ def build_session_summaries(events: list[EventRow]) -> list[SessionSummary]: if event_type == "buyin": entry.buy_in_cents += event["amount_cents"] + elif event_type == "front": + entry.front_cents += event["amount_cents"] elif event_type == "cashout": entry.cash_out_cents += event["amount_cents"] elif event_type == "paid": @@ -304,9 +331,7 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]: run_summary = summarize_player_runs(entries) wins = [value for value in nets if value > 0] losses = [value for value in nets if value < 0] - total_buy_in = sum(entry.buy_in_cents for entry in entries) - total_cash_out = sum(entry.cash_out_cents for entry in entries) - total_net = total_cash_out - total_buy_in + sessions_played = len(entries) winning_sessions = len(wins) losing_sessions = len(losses) @@ -318,7 +343,13 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]: ) biggest_win = max(wins) if wins else 0 biggest_loss = min(losses) if losses else 0 - roi_pct = (total_net / total_buy_in * 100) if total_buy_in else 0.0 + + total_buy_in = sum(entry.buy_in_cents for entry in entries) + total_front = sum(entry.front_cents for entry in entries) + total_invested = sum(entry.invested_cents for entry in entries) + total_cash_out = sum(entry.cash_out_cents for entry in entries) + total_net = sum(entry.net_cents for entry in entries) + roi_pct = (total_net / total_invested * 100) if total_invested else 0.0 leaderboard.append( PlayerStats( @@ -333,6 +364,8 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]: biggest_win_cents=biggest_win, biggest_loss_cents=biggest_loss, total_buy_in_cents=total_buy_in, + total_front_cents=total_front, + total_invested_cents=total_invested, total_cash_out_cents=total_cash_out, total_net_cents=total_net, roi_pct=roi_pct, diff --git a/storage.py b/storage.py index 985445f..b825ab8 100644 --- a/storage.py +++ b/storage.py @@ -33,6 +33,7 @@ class EventRow(TypedDict): VALID_EVENT_TYPES = { "buyin", "cashout", + "front", "paid", "note", "session_open", @@ -91,7 +92,7 @@ def append_event( if normalized_type not in VALID_EVENT_TYPES: raise ValueError(f"Unsupported event type: {event_type}") - if normalized_type == "note": + if normalized_type == {"note", "session_open", "session_close"}: amount_cents = 0 event = EventRow( diff --git a/templates/admin_dashboard.html b/templates/admin_dashboard.html index 5255252..5d5f19e 100644 --- a/templates/admin_dashboard.html +++ b/templates/admin_dashboard.html @@ -33,6 +33,7 @@ Event type Buy-in + Front Cash-out Paid out Note only @@ -46,7 +47,7 @@ Note - + Add event diff --git a/templates/session_detail.html b/templates/session_detail.html index df4b296..ed15c8b 100644 --- a/templates/session_detail.html +++ b/templates/session_detail.html @@ -20,9 +20,9 @@ - + - Total cash-out due + Total payout due {{ session.total_payout_due_cents | money }} @@ -33,10 +33,22 @@ Total remaining - + {{ session.total_remaining_cents | money }} + + + Total fronted + {{ session.total_front_cents | money }} + + + + Players still owe + + {{ session.total_player_owes_cents | money }} + + @@ -53,6 +65,7 @@ Player Buy-in + Fronted Cash-out Net Status @@ -70,6 +83,7 @@ {{ entry.buy_in_cents | money }} + {{ entry.front_cents | money }} {{ entry.cash_out_cents | money }} {{ entry.net_cents | money }}