implement rollover tracking + simple view session summary

This commit is contained in:
SowinskiBraeden committed 2026-03-22 13:52:59 -07:00
1 parent 3248731f51
commit c24f7d5810
8 files changed
+348 -113

No files matched your search

+109 -41
View File
@@ -28,6 +28,7 @@ from stats import (
safe_date_label,
session_breakdown_series,
session_events,
session_label,
unique_player_names,
)
from storage import append_event, ensure_data_file, load_events
@@ -52,6 +53,22 @@ def load_local_env(env_path: Path) -> None:
os.environ.setdefault(key, value)
def next_session_id(sessions, session_date: str) -> str:
matching = [session for session in sessions if session.session_date == session_date]
highest = 0
for s in matching:
if s.session_id == session_date:
highest = max(highest, 1)
continue
suffix = s.session_id.replace(f"{session_date}-", "")
if suffix.isdigit():
highest = max(highest, int(suffix))
return f"{session_date}-{highest + 1:02d}"
load_local_env(ENV_PATH)
app = Flask(__name__)
@@ -126,21 +143,26 @@ def leaderboard() -> str:
@app.get("/sessions")
def sessions() -> str:
def sessions():
events = load_events(DATA_PATH)
session_summaries = build_session_summaries(events)
return render_template("sessions.html", sessions=session_summaries)
sessions = build_session_summaries(events)
return render_template(
"sessions.html",
sessions=sessions,
session_label=session_label,
)
@app.get("/sessions/<session_date>")
def session_detail(session_date: str) -> str:
@app.get("/sessions/<session_id>")
def session_detail(session_id: str) -> str:
events = load_events(DATA_PATH)
sessions = build_session_summaries(events)
target_session = next(
(
session_summary
for session_summary in sessions
if session_summary.session_date == session_date
if session_summary.session_id == session_id
),
None,
)
@@ -148,27 +170,33 @@ def session_detail(session_date: str) -> str:
flash("That session was not found.", "error")
return redirect(url_for("sessions"))
# sessions are in reverse order
idx = sessions.index(target_session)
next_session_idx = idx - 1
prev_session_idx = idx + 1
target_index = next(
(
index
for index, session in enumerate(sessions)
if session.session_id == session_id
),
None,
)
next_session = None
prev_session = None
if target_index is None:
flash("That session was not found.", "error")
return redirect(url_for("sessions"))
if next_session_idx >= 0:
next_session = sessions[next_session_idx]
if prev_session_idx < len(sessions):
prev_session = sessions[prev_session_idx]
target_session = sessions[target_index]
prev_session = (
sessions[target_index + 1] if target_index < len(sessions) - 1 else None
)
next_session = sessions[target_index - 1] if target_index > 0 else None
return render_template(
"session_detail.html",
session=target_session,
next_session=next_session,
prev_session=prev_session,
raw_events=session_events(events, session_date),
raw_events=session_events(events, session_id),
chart_data=session_breakdown_series(target_session),
session_label=session_label,
)
@@ -224,11 +252,16 @@ def admin_session_state() -> str:
flash("Admin login required.", "error")
return redirect(url_for("admin_login"))
session_date = request.form.get("session_date", "").strip()
session_id = request.form.get("session_id", "").strip()
state = request.form.get("state", "").strip()
if not session_date:
flash("Session date is required.", "error")
events = load_events(DATA_PATH)
sessions = build_session_summaries(events)
by_id = {session.session_id: session for session in sessions}
target = by_id.get(session_id)
if target is None:
flash("Session not found.", "error")
return redirect(url_for("admin_dashboard"))
if state not in {"open", "closed"}:
@@ -237,13 +270,15 @@ def admin_session_state() -> str:
append_event(
DATA_PATH,
session_date=session_date,
session_id=target.session_id,
session_date=target.session_date,
amount_cents=0,
player_name="",
event_type="session_open" if state == "open" else "session_close",
amount_cents=0,
note=f"Session marked {state}.",
actor=app.config["ADMIN_USERNAME"],
)
flash(f"Session marked {state}.", "success")
return redirect(url_for("admin_dashboard"))
@@ -329,64 +364,97 @@ def admin_import_csv():
return redirect(url_for("admin_dashboard"))
@app.route("/admin", methods=["GET", "POST"])
def admin_dashboard() -> str:
@app.post("/admin/open-session")
def admin_open_session() -> str:
if not is_admin():
flash("Admin login required.", "error")
return redirect(url_for("admin_login"))
session_date = request.form.get("session_date", "").strip()
if not session_date:
flash("Session date is required.", "error")
return redirect(url_for("admin_dashboard"))
events = load_events(DATA_PATH)
sessions = build_session_summaries(events)
session_id = next_session_id(sessions, session_date)
append_event(
DATA_PATH,
session_id=session_id,
session_date=session_date,
amount_cents=0,
player_name="",
event_type="session_open",
note="Session opened.",
actor=app.config["ADMIN_USERNAME"],
)
flash(f"Opened session {session_id}.", "success")
return redirect(url_for("admin_dashboard"))
@app.route("/admin", methods=["GET", "POST"])
def admin_dashboard():
if not is_admin():
return redirect(url_for("admin_login"))
if request.method == "POST":
session_date = request.form.get("session_date", "").strip()
session_id = request.form.get("session_id", "").strip()
player_name = request.form.get("player_name", "").strip()
event_type = request.form.get("event_type", "").strip()
amount_raw = request.form.get("amount", "0").strip()
note = request.form.get("note", "").strip()
if not session_date or not player_name or not event_type:
flash("Session date, player name, and event type are required.", "error")
return redirect(url_for("admin_dashboard"))
amount_raw = request.form.get("amount", "0").strip()
try:
amount_cents = 0 if event_type == "note" else round(float(amount_raw) * 100)
amount_cents = int(round(float(amount_raw) * 100))
except ValueError:
flash("Amount must be a valid number.", "error")
flash("Amount must be a number.", "error")
return redirect(url_for("admin_dashboard"))
events = load_events(DATA_PATH)
sessions = build_session_summaries(events)
status_by_date = {session.session_date: session.status for session in sessions}
by_id = {session.session_id: session for session in sessions}
target = by_id.get(session_id)
if status_by_date.get(session_date, "closed") != "open":
if target is None:
flash("Select a valid open session.", "error")
return redirect(url_for("admin_dashboard"))
if target.status != "open":
flash(
"That session is closed. Open it first before adding player events.",
"That session is closed. Reopen it first if you need to add events.",
"error",
)
return redirect(url_for("admin_dashboard"))
append_event(
DATA_PATH,
session_date=session_date,
session_id=target.session_id,
session_date=target.session_date,
player_name=player_name,
event_type=event_type,
amount_cents=amount_cents,
note=note,
actor=app.config["ADMIN_USERNAME"],
)
flash("Event added to the ledger.", "success")
flash("Event added.", "success")
return redirect(url_for("admin_dashboard"))
events = load_events(DATA_PATH)
sessions = build_session_summaries(events)
recent_sessions = sessions[:6]
open_sessions = [session for session in sessions if session.status == "open"]
recent_events = list(reversed(events[-8:]))
recent_sessions = sessions[:8]
recent_events = list(reversed(events[-20:]))
return render_template(
"admin_dashboard.html",
recent_sessions=recent_sessions,
open_sessions=open_sessions,
recent_sessions=recent_sessions,
recent_events=recent_events,
player_names=unique_player_names(events),
session_label=session_label,
)
+15
View File
@@ -1054,6 +1054,21 @@ td a:hover {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.table-toolbar {
display: flex;
gap: 0.65rem;
margin-bottom: 1rem;
}
.table-view-toggle.is-active {
background: rgba(255, 166, 77, 0.12);
border-color: rgba(255, 166, 77, 0.28);
}
.session-results-table.compact .detail-col {
display: none;
}
@media (max-width: 1100px) {
.payout-summary-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
+104 -35
View File
@@ -26,17 +26,20 @@ PLAYER_PALETTE = [
@dataclass
class SessionEntry:
session_id: str
session_date: str
player_name: str
buy_in_cents: int = 0
front_cents: int = 0
cash_out_cents: int = 0
paid_cents: int = 0
rollover_in_cents: int = 0
rollover_out_cents: int = 0
notes: list[str] = field(default_factory=list)
@property
def invested_cents(self) -> int:
return self.buy_in_cents + self.front_cents
return self.buy_in_cents + self.front_cents + self.rollover_in_cents
@property
def net_cents(self) -> int:
@@ -47,31 +50,45 @@ class SessionEntry:
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)
def payout_remaining_cents(self) -> int:
return max(self.payout_due_cents - self.paid_cents - self.rollover_out_cents, 0)
@property
def payout_remaining_cents(self) -> int:
return max(self.payout_due_cents - self.paid_cents, 0)
def gross_payout_cents(self) -> int:
return max(self.cash_out_cents - self.front_cents, 0)
@property
def settled_cents(self) -> int:
return self.paid_cents + self.rollover_out_cents
@property
def current_due_cents(self) -> int:
return max(self.gross_payout_cents - self.settled_cents, 0)
@property
def player_owes_cents(self) -> int:
return max(self.front_cents - self.cash_out_cents, 0)
@property
def payout_status(self) -> str:
if self.player_owes_cents > 0:
return "owes"
if self.payout_due_cents <= 0:
if self.gross_payout_cents <= 0:
return "none"
if self.paid_cents <= 0:
return "unpaid"
if self.payout_remaining_cents <= 0:
if self.current_due_cents <= 0:
return "paid"
if self.settled_cents <= 0:
return "unpaid"
return "partial"
@dataclass
class SessionSummary:
session_id: str
session_date: str
entries: list[SessionEntry]
status: str = "closed"
opened_at: str = ""
@property
def is_open(self) -> bool:
@@ -85,6 +102,10 @@ class SessionSummary:
def total_front_cents(self) -> int:
return sum(entry.front_cents for entry in self.entries)
@property
def total_rollover_in_cents(self) -> int:
return sum(entry.rollover_in_cents for entry in self.entries)
@property
def total_invested_cents(self) -> int:
return sum(entry.invested_cents for entry in self.entries)
@@ -93,18 +114,10 @@ class SessionSummary:
def total_cash_out_cents(self) -> int:
return sum(entry.cash_out_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)
@@ -113,6 +126,26 @@ class SessionSummary:
def total_net_cents(self) -> int:
return sum(entry.net_cents for entry in self.entries)
@property
def total_gross_payout_cents(self) -> int:
return sum(entry.gross_payout_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_rollover_out_cents(self) -> int:
return sum(entry.rollover_out_cents for entry in self.entries)
@property
def total_current_due_cents(self) -> int:
return sum(entry.current_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)
@dataclass
class PlayerStats:
@@ -128,8 +161,11 @@ class PlayerStats:
biggest_loss_cents: int
total_buy_in_cents: int
total_front_cents: int
total_rollover_in_cents: int
total_invested_cents: int
total_cash_out_cents: int
total_paid_cents: int
total_rollover_out_cents: int
total_net_cents: int
roi_pct: float
current_win_streak: int
@@ -164,11 +200,21 @@ def cents_to_dollars(cents: int) -> str:
return f"${value:,.2f}"
def safe_date_label(session_date: str) -> str:
def safe_date_label(raw_date: str) -> str:
try:
return datetime.strptime(session_date, "%Y-%m-%d").strftime("%b %d, %Y")
return datetime.strptime(raw_date, "%Y-%m-%d").strftime("%b %d, %Y")
except ValueError:
return session_date
return raw_date
def session_label(session: SessionSummary) -> str:
if session.session_id == session.session_date:
return safe_date_label(session.session_date)
suffix = session.session_id.replace(f"{session.session_date}-", "")
if suffix.isdigit():
return f"{safe_date_label(session.session_date)} · S{int(suffix)}"
return f"{safe_date_label(session.session_date)} · {suffix}"
def color_for_name(name: str, names: list[str]) -> str:
@@ -191,32 +237,36 @@ def build_session_summaries(events: list[EventRow]) -> list[SessionSummary]:
grouped: dict[tuple[str, str], SessionEntry] = {}
by_session: dict[str, list[SessionEntry]] = defaultdict(list)
session_status: dict[str, str] = {}
session_dates_seen: set[str] = set()
session_dates: dict[str, str] = {}
session_opened_at: dict[str, str] = {}
for event in events:
session_date = event["session_date"]
event_type = event["event_type"]
session_id = event["session_id"].strip() or event["session_date"].strip()
session_date = event["session_date"].strip()
event_type = event["event_type"].strip()
if not session_date:
if not session_id or not session_date:
continue
session_dates_seen.add(session_date)
session_dates[session_id] = session_date
session_opened_at.setdefault(session_id, event["created_at"])
if event_type == "session_open":
session_status[session_date] = "open"
session_status[session_id] = "open"
continue
if event_type == "session_close":
session_status[session_date] = "closed"
session_status[session_id] = "closed"
continue
player_name = event["player_name"].strip()
if not player_name:
continue
key = (session_date, player_name)
key = (session_id, player_name)
if key not in grouped:
grouped[key] = SessionEntry(
session_id=session_id,
session_date=session_date,
player_name=player_name,
)
@@ -227,33 +277,46 @@ def build_session_summaries(events: list[EventRow]) -> list[SessionSummary]:
entry.buy_in_cents += event["amount_cents"]
elif event_type == "front":
entry.front_cents += event["amount_cents"]
elif event_type == "rollover_in":
entry.rollover_in_cents += event["amount_cents"]
elif event_type == "cashout":
entry.cash_out_cents += event["amount_cents"]
elif event_type == "paid":
entry.paid_cents += event["amount_cents"]
elif event_type == "rollover_out":
entry.rollover_out_cents += event["amount_cents"]
if event["note"]:
entry.notes.append(event["note"])
for entry in grouped.values():
by_session[entry.session_date].append(entry)
by_session[entry.session_id].append(entry)
sessions: list[SessionSummary] = []
for session_date in session_dates_seen:
for session_id, session_date in session_dates.items():
entries = sorted(
by_session.get(session_date, []),
by_session.get(session_id, []),
key=lambda entry: entry.player_name.casefold(),
)
sessions.append(
SessionSummary(
session_id=session_id,
session_date=session_date,
entries=entries,
status=session_status.get(session_date, "closed"),
status=session_status.get(session_id, "closed"),
opened_at=session_opened_at.get(session_id, ""),
)
)
sessions.sort(key=lambda session: session.session_date, reverse=True)
sessions.sort(
key=lambda session: (
session.session_date,
session.opened_at,
session.session_id,
),
reverse=True,
)
return sessions
@@ -346,8 +409,11 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]:
total_buy_in = sum(entry.buy_in_cents for entry in entries)
total_front = sum(entry.front_cents for entry in entries)
total_rollover_in = sum(entry.rollover_in_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_paid = sum(entry.paid_cents for entry in entries)
total_rollover_out = sum(entry.rollover_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
@@ -365,8 +431,11 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]:
biggest_loss_cents=biggest_loss,
total_buy_in_cents=total_buy_in,
total_front_cents=total_front,
total_rollover_in_cents=total_rollover_in,
total_invested_cents=total_invested,
total_cash_out_cents=total_cash_out,
total_paid_cents=total_paid,
total_rollover_out_cents=total_rollover_out,
total_net_cents=total_net,
roi_pct=roi_pct,
current_win_streak=run_summary["current_win_streak"],
@@ -496,8 +565,8 @@ def session_breakdown_series(session: SessionSummary) -> dict[str, Any]:
}
def session_events(events: list[EventRow], session_date: str) -> list[EventRow]:
return [event for event in events if event["session_date"] == session_date]
def session_events(events: list[EventRow], session_id: str) -> list[EventRow]:
return [event for event in events if event["session_id"] == session_id]
def unique_player_names(events: list[EventRow]) -> list[str]:
+8 -1
View File
@@ -10,6 +10,7 @@ from typing import TypedDict
CSV_HEADERS = [
"id",
"created_at",
"session_id",
"session_date",
"player_name",
"event_type",
@@ -22,6 +23,7 @@ CSV_HEADERS = [
class EventRow(TypedDict):
id: str
created_at: str
session_id: str
session_date: str
player_name: str
event_type: str
@@ -32,9 +34,11 @@ class EventRow(TypedDict):
VALID_EVENT_TYPES = {
"buyin",
"cashout",
"front",
"cashout",
"paid",
"rollover_in",
"rollover_out",
"note",
"session_open",
"session_close",
@@ -62,6 +66,7 @@ def load_events(csv_path: Path) -> list[EventRow]:
EventRow(
id=row["id"],
created_at=row["created_at"],
session_id=row["session_id"],
session_date=row["session_date"],
player_name=row["player_name"],
event_type=row["event_type"],
@@ -79,6 +84,7 @@ def load_events(csv_path: Path) -> list[EventRow]:
def append_event(
csv_path: Path,
session_id: str,
session_date: str,
player_name: str,
event_type: str,
@@ -98,6 +104,7 @@ def append_event(
event = EventRow(
id=str(uuid.uuid4()),
created_at=datetime.now(timezone.utc).isoformat(),
session_id=session_id.strip(),
session_date=session_date.strip(),
player_name=player_name.strip(),
event_type=normalized_type,
+17 -9
View File
@@ -15,8 +15,15 @@
<h2>Add to the ledger</h2>
<label>
<span>Session date</span>
<input type="date" name="session_date" required>
<span>Open session</span>
<select name="session_id" required>
<option value="">Select session</option>
{% for session in open_sessions %}
<option value="{{ session.session_id }}">
{{ session_label(session) }}
</option>
{% endfor %}
</select>
</label>
<label>
@@ -34,8 +41,10 @@
<select name="event_type" required>
<option value="buyin">Buy-in</option>
<option value="front">Front</option>
<option value="rollover_in">Rollover in</option>
<option value="cashout">Cash-out</option>
<option value="paid">Paid out</option>
<option value="rollover_out">Rollover out</option>
<option value="note">Note only</option>
</select>
</label>
@@ -54,9 +63,9 @@
</form>
<section class="grid">
<form class="panel form-card" method="post" action="{{ url_for('admin_session_state') }}">
<form class="panel form-card" method="post" action="{{ url_for('admin_open_session') }}">
<p class="eyebrow">Session state</p>
<h2>Open or close a session</h2>
<h2>Open a new session</h2>
<label>
<span>Session date</span>
@@ -64,8 +73,7 @@
</label>
<div class="button-row">
<button class="primary-button" type="submit" name="state" value="open">Open session</button>
<button class="secondary-button" type="submit" name="state" value="closed">Close session</button>
<button class="primary-button" type="submit">Open session</button>
</div>
</form>
@@ -130,8 +138,8 @@
{% for session in recent_sessions %}
<tr>
<td>
<a href="{{ url_for('session_detail', session_date=session.session_date) }}">
{{ session.session_date | pretty_date }}
<a href="{{ url_for('session_detail', session_id=session.session_id) }}">
{{ session_label(session) }}
</a>
</td>
<td>{{ session.entries|length }}</td>
@@ -144,7 +152,7 @@
</td>
<td>
<form method="post" action="{{ url_for('admin_session_state') }}" class="inline-action-form">
<input type="hidden" name="session_date" value="{{ session.session_date }}">
<input type="hidden" name="session_id" value="{{ session.session_id }}">
{% if session.status == "open" %}
<button class="table-action-button" type="submit" name="state" value="closed">Close</button>
{% else %}
+7 -1
View File
@@ -14,8 +14,9 @@
<article class="panel stat-card"><span>Win %</span><strong>{{ "%.1f"|format(player.win_pct) }}%</strong></article>
<article class="panel stat-card"><span>ROI</span><strong>{{ "%.1f"|format(player.roi_pct) }}%</strong></article>
<article class="panel stat-card"><span>Total invested</span><strong>{{ player.total_invested_cents | money }}</strong></article>
<article class="panel stat-card"><span>Paid Buy-ins</span><strong>{{ player.total_buy_in_cents | money }}</strong></article>
<article class="panel stat-card"><span>Paid Buy-in</span><strong>{{ player.total_buy_in_cents | money }}</strong></article>
<article class="panel stat-card"><span>Fronted</span><strong>{{ player.total_front_cents | money }}</strong></article>
<!--<article class="panel stat-card"><span>Rolled in</span><strong>{{ player.total_rollover_in_cents | money }}</strong></article>-->
<article class="panel stat-card"><span>Cash-outs</span><strong>{{ player.total_cash_out_cents | money }}</strong></article>
<article class="panel stat-card"><span>Sessions</span><strong>{{ player.sessions_played }}</strong></article>
<article class="panel stat-card">
@@ -84,6 +85,11 @@
<div class="table-wrap">
<table>
<tbody>
<tr><th>Paid Buy-ins</th><td>{{ player.total_buy_in_cents | money }}</td></tr>
<tr><th>Fronted</th><td>{{ player.total_front_cents | money }}</td></tr>
<tr><th>Rolled In</th><td>{{ player.total_rollover_in_cents | money }}</td></tr>
<tr><th>Total Invested</th><td>{{ player.total_invested_cents | money }}</td></tr>
<tr><th>Rolled Out</th><td>{{ player.total_rollover_out_cents | money }}</td></tr>
<tr><th>Winning Sessions</th><td>{{ player.winning_sessions }}</td></tr>
<tr><th>Losing Sessions</th><td>{{ player.losing_sessions }}</td></tr>
<tr><th>Break-even Sessions</th><td>{{ player.break_even_sessions }}</td></tr>
+87 -25
View File
@@ -5,16 +5,21 @@
<div style="display: flex; justify-content: space-between;">
<div>
<p class="eyebrow">Session detail</p>
<h1>{{ session.session_date | pretty_date }}</h1>
<h1>{{ session_label(session) }}</h1>
<p class="muted-text">Current totals are derived from the event log for this date.</p>
</div>
<div style="align-content: center;">
<div style="align-self: center;">
{% if prev_session %}
<a class="secondary-button" href="{{ url_for('session_detail', session_date=prev_session.session_date) }}">Previous Session</a>
<a class="secondary-button" href="{{ url_for('session_detail', session_id=prev_session.session_id) }}">
Previous Session
</a>
{% endif %}
{% if next_session %}
<a class="secondary-button" href="{{ url_for('session_detail', session_date=next_session.session_date) }}">Next Session</a>
<a class="secondary-button" href="{{ url_for('session_detail', session_id=next_session.session_id) }}">
Next Session
</a>
{% endif %}
</div>
</div>
@@ -22,8 +27,13 @@
<section class="grid payout-summary-grid">
<article class="panel stat-card">
<span>Total payout due</span>
<strong>{{ session.total_payout_due_cents | money }}</strong>
<span>Gross payout</span>
<strong>{{ session.total_gross_payout_cents | money }}</strong>
</article>
<article class="panel stat-card">
<span>Total rolled out</span>
<strong>{{ session.total_rollover_out_cents | money }}</strong>
</article>
<article class="panel stat-card">
@@ -32,17 +42,12 @@
</article>
<article class="panel stat-card">
<span>Total remaining</span>
<strong class="{{ 'negative' if session.total_remaining_cents > 0 else 'positive' }}">
{{ session.total_remaining_cents | money }}
<span>Still owed</span>
<strong class="{{ 'negative' if session.total_current_due_cents > 0 else 'positive' }}">
{{ session.total_current_due_cents | money }}
</strong>
</article>
<article class="panel stat-card">
<span>Total fronted</span>
<strong>{{ session.total_front_cents | money }}</strong>
</article>
<article class="panel stat-card">
<span>Players still owe</span>
<strong class="{{ 'negative' if session.total_player_owes_cents > 0 else '' }}">
@@ -51,12 +56,22 @@
</article>
</section>
<section class="panel results-panel results-panel-full">
<div class="panel-header">
<div>
<p class="eyebrow">Results</p>
<h2>Player totals</h2>
</div>
<div class="table-toolbar">
<button type="button" class="secondary-button table-view-toggle is-active" data-view="compact">
Compact
</button>
<button type="button" class="secondary-button table-view-toggle" data-view="full">
Full details
</button>
</div>
</div>
<div class="table-scroll table-scroll-open">
@@ -64,13 +79,16 @@
<thead>
<tr>
<th>Player</th>
<th>Buy-in</th>
<th>Fronted</th>
<th>Cash-out</th>
<th>In</th>
<th class="detail-col">Fronted</th>
<th class="detail-col">Rolled in</th>
<th>Out</th>
<th>Net</th>
<th class="detail-col">Gross due</th>
<th class="detail-col">Paid</th>
<th class="detail-col">Rolled out</th>
<th>Still owed</th>
<th>Status</th>
<th>Paid</th>
<th>Remaining</th>
<th>Notes</th>
</tr>
</thead>
@@ -82,21 +100,41 @@
{{ entry.player_name }}
</a>
</td>
<td>{{ entry.buy_in_cents | money }}</td>
<td>{{ entry.front_cents | money }}</td>
<td>{{ entry.invested_cents | money }}</td>
<td class="detail-col">{{ entry.front_cents | money }}</td>
<td class="detail-col">{{ entry.rollover_in_cents | money }}</td>
<td>{{ entry.cash_out_cents | money }}</td>
<td class="{{ 'positive' if entry.net_cents > 0 else 'negative' if entry.net_cents < 0 else '' }}">
{{ entry.net_cents | money }}
</td>
<td class="detail-col">
{% if entry.player_owes_cents > 0 %}
{% else %}
{{ entry.gross_payout_cents | money }}
{% endif %}
</td>
<td class="detail-col">{{ entry.paid_cents | money }}</td>
<td class="detail-col">{{ entry.rollover_out_cents | money }}</td>
<td class="{% if entry.player_owes_cents > 0 %}negative{% elif entry.current_due_cents > 0 %}negative{% else %}positive{% endif %}">
{% if entry.player_owes_cents > 0 %}
Owes {{ entry.player_owes_cents | money }}
{% else %}
{{ entry.current_due_cents | money }}
{% endif %}
</td>
<td>
<span class="status-badge status-{{ entry.payout_status }}">
{{ entry.payout_status }}
</span>
</td>
<td class="{{ 'positive' if entry.paid_cents > 0 else '' }}">{{ entry.paid_cents | money }}</td>
<td class="{{ 'negative' if entry.payout_remaining_cents > 0 else '' }}">
{{ entry.payout_remaining_cents | money }}
</td>
<td>
{% if entry.notes %}
<ul class="note-list">
@@ -270,4 +308,28 @@
});
}
</script>
<script>
const sessionTable = document.querySelector('.session-results-table');
const viewButtons = document.querySelectorAll('.table-view-toggle');
if (sessionTable && viewButtons.length) {
sessionTable.classList.add('compact');
viewButtons.forEach((button) => {
button.addEventListener('click', () => {
const view = button.dataset.view;
viewButtons.forEach((btn) => btn.classList.remove('is-active'));
button.classList.add('is-active');
if (view === 'full') {
sessionTable.classList.remove('compact');
} else {
sessionTable.classList.add('compact');
}
});
});
}
</script>
{% endblock %}
+1 -1
View File
@@ -31,7 +31,7 @@
<tbody>
{% for session in sessions %}
<tr>
<td><a href="{{ url_for('session_detail', session_date=session.session_date) }}">{{ session.session_date | pretty_date }}</a></td>
<td><a href="{{ url_for('session_detail', session_id=session.session_id) }}">{{ session_label(session) }}</a></td>
<td><span class="status-badge status-{{ session.status }}">
{{ session.status }}
</span></td>