front chips logic
This commit is contained in:
1 parent
ce795d68cd
commit
f0f6ac8e69
5 files changed
+102
-19
No files matched your search
@@ -322,6 +322,10 @@ h2 {
|
|||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.four-col {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.stack-gap {
|
.stack-gap {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
@@ -944,6 +948,12 @@ td a:hover {
|
|||||||
border: 1px solid rgba(96, 165, 250, 0.22);
|
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 {
|
.pill-note {
|
||||||
color: #cbd5e1;
|
color: #cbd5e1;
|
||||||
background: rgba(203, 213, 225, 0.08);
|
background: rgba(203, 213, 225, 0.08);
|
||||||
@@ -963,6 +973,12 @@ td a:hover {
|
|||||||
text-transform: uppercase;
|
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 {
|
.inline-action-form {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -1032,6 +1048,24 @@ td a:hover {
|
|||||||
border-color: rgba(255, 166, 77, 0.28);
|
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) {
|
@media (max-width: 980px) {
|
||||||
.site-shell {
|
.site-shell {
|
||||||
width: min(100% - 24px, 100%);
|
width: min(100% - 24px, 100%);
|
||||||
|
|||||||
@@ -29,17 +29,26 @@ class SessionEntry:
|
|||||||
session_date: str
|
session_date: str
|
||||||
player_name: str
|
player_name: str
|
||||||
buy_in_cents: int = 0
|
buy_in_cents: int = 0
|
||||||
|
front_cents: int = 0
|
||||||
cash_out_cents: int = 0
|
cash_out_cents: int = 0
|
||||||
paid_cents: int = 0
|
paid_cents: int = 0
|
||||||
notes: list[str] = field(default_factory=list)
|
notes: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def invested_cents(self) -> int:
|
||||||
|
return self.buy_in_cents + self.front_cents
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def net_cents(self) -> int:
|
def net_cents(self) -> int:
|
||||||
return self.cash_out_cents - self.buy_in_cents
|
return self.cash_out_cents - self.invested_cents
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def payout_due_cents(self) -> int:
|
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
|
@property
|
||||||
def payout_remaining_cents(self) -> int:
|
def payout_remaining_cents(self) -> int:
|
||||||
@@ -47,6 +56,8 @@ class SessionEntry:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def payout_status(self) -> str:
|
def payout_status(self) -> str:
|
||||||
|
if self.player_owes_cents > 0:
|
||||||
|
return "owes"
|
||||||
if self.payout_due_cents <= 0:
|
if self.payout_due_cents <= 0:
|
||||||
return "none"
|
return "none"
|
||||||
if self.paid_cents <= 0:
|
if self.paid_cents <= 0:
|
||||||
@@ -70,26 +81,38 @@ class SessionSummary:
|
|||||||
def total_buy_in_cents(self) -> int:
|
def total_buy_in_cents(self) -> int:
|
||||||
return sum(entry.buy_in_cents for entry in self.entries)
|
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
|
@property
|
||||||
def total_cash_out_cents(self) -> int:
|
def total_cash_out_cents(self) -> int:
|
||||||
return sum(entry.cash_out_cents for entry in self.entries)
|
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
|
@property
|
||||||
def total_paid_cents(self) -> int:
|
def total_paid_cents(self) -> int:
|
||||||
return sum(entry.paid_cents for entry in self.entries)
|
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
|
@property
|
||||||
def total_remaining_cents(self) -> int:
|
def total_remaining_cents(self) -> int:
|
||||||
return sum(entry.payout_remaining_cents for entry in self.entries)
|
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
|
@dataclass
|
||||||
class PlayerStats:
|
class PlayerStats:
|
||||||
@@ -104,6 +127,8 @@ class PlayerStats:
|
|||||||
biggest_win_cents: int
|
biggest_win_cents: int
|
||||||
biggest_loss_cents: int
|
biggest_loss_cents: int
|
||||||
total_buy_in_cents: int
|
total_buy_in_cents: int
|
||||||
|
total_front_cents: int
|
||||||
|
total_invested_cents: int
|
||||||
total_cash_out_cents: int
|
total_cash_out_cents: int
|
||||||
total_net_cents: int
|
total_net_cents: int
|
||||||
roi_pct: float
|
roi_pct: float
|
||||||
@@ -200,6 +225,8 @@ def build_session_summaries(events: list[EventRow]) -> list[SessionSummary]:
|
|||||||
|
|
||||||
if event_type == "buyin":
|
if event_type == "buyin":
|
||||||
entry.buy_in_cents += event["amount_cents"]
|
entry.buy_in_cents += event["amount_cents"]
|
||||||
|
elif event_type == "front":
|
||||||
|
entry.front_cents += event["amount_cents"]
|
||||||
elif event_type == "cashout":
|
elif event_type == "cashout":
|
||||||
entry.cash_out_cents += event["amount_cents"]
|
entry.cash_out_cents += event["amount_cents"]
|
||||||
elif event_type == "paid":
|
elif event_type == "paid":
|
||||||
@@ -304,9 +331,7 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]:
|
|||||||
run_summary = summarize_player_runs(entries)
|
run_summary = summarize_player_runs(entries)
|
||||||
wins = [value for value in nets if value > 0]
|
wins = [value for value in nets if value > 0]
|
||||||
losses = [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)
|
sessions_played = len(entries)
|
||||||
winning_sessions = len(wins)
|
winning_sessions = len(wins)
|
||||||
losing_sessions = len(losses)
|
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_win = max(wins) if wins else 0
|
||||||
biggest_loss = min(losses) if losses 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(
|
leaderboard.append(
|
||||||
PlayerStats(
|
PlayerStats(
|
||||||
@@ -333,6 +364,8 @@ def build_leaderboard(sessions: list[SessionSummary]) -> list[PlayerStats]:
|
|||||||
biggest_win_cents=biggest_win,
|
biggest_win_cents=biggest_win,
|
||||||
biggest_loss_cents=biggest_loss,
|
biggest_loss_cents=biggest_loss,
|
||||||
total_buy_in_cents=total_buy_in,
|
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_cash_out_cents=total_cash_out,
|
||||||
total_net_cents=total_net,
|
total_net_cents=total_net,
|
||||||
roi_pct=roi_pct,
|
roi_pct=roi_pct,
|
||||||
|
|||||||
+2
-1
@@ -33,6 +33,7 @@ class EventRow(TypedDict):
|
|||||||
VALID_EVENT_TYPES = {
|
VALID_EVENT_TYPES = {
|
||||||
"buyin",
|
"buyin",
|
||||||
"cashout",
|
"cashout",
|
||||||
|
"front",
|
||||||
"paid",
|
"paid",
|
||||||
"note",
|
"note",
|
||||||
"session_open",
|
"session_open",
|
||||||
@@ -91,7 +92,7 @@ def append_event(
|
|||||||
if normalized_type not in VALID_EVENT_TYPES:
|
if normalized_type not in VALID_EVENT_TYPES:
|
||||||
raise ValueError(f"Unsupported event type: {event_type}")
|
raise ValueError(f"Unsupported event type: {event_type}")
|
||||||
|
|
||||||
if normalized_type == "note":
|
if normalized_type == {"note", "session_open", "session_close"}:
|
||||||
amount_cents = 0
|
amount_cents = 0
|
||||||
|
|
||||||
event = EventRow(
|
event = EventRow(
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<span>Event type</span>
|
<span>Event type</span>
|
||||||
<select name="event_type" required>
|
<select name="event_type" required>
|
||||||
<option value="buyin">Buy-in</option>
|
<option value="buyin">Buy-in</option>
|
||||||
|
<option value="front">Front</option>
|
||||||
<option value="cashout">Cash-out</option>
|
<option value="cashout">Cash-out</option>
|
||||||
<option value="paid">Paid out</option>
|
<option value="paid">Paid out</option>
|
||||||
<option value="note">Note only</option>
|
<option value="note">Note only</option>
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
|
|
||||||
<label>
|
<label>
|
||||||
<span>Note</span>
|
<span>Note</span>
|
||||||
<textarea name="note" rows="4" placeholder="Rebuy, correction, partial payout, e-transfer sent, cash paid, etc."></textarea>
|
<textarea name="note" rows="4" placeholder="Rebuy, fronted chips, correction, partial payout, e-transfer sent, cash paid, etc."></textarea>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button class="primary-button" type="submit">Add event</button>
|
<button class="primary-button" type="submit">Add event</button>
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="grid three-col">
|
<section class="grid payout-summary-grid">
|
||||||
<article class="panel stat-card">
|
<article class="panel stat-card">
|
||||||
<span>Total cash-out due</span>
|
<span>Total payout due</span>
|
||||||
<strong>{{ session.total_payout_due_cents | money }}</strong>
|
<strong>{{ session.total_payout_due_cents | money }}</strong>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
@@ -33,10 +33,22 @@
|
|||||||
|
|
||||||
<article class="panel stat-card">
|
<article class="panel stat-card">
|
||||||
<span>Total remaining</span>
|
<span>Total remaining</span>
|
||||||
<strong class="{{ 'negative' if session.total_remaining_cents > 0 else '' }}">
|
<strong class="{{ 'negative' if session.total_remaining_cents > 0 else 'positive' }}">
|
||||||
{{ session.total_remaining_cents | money }}
|
{{ session.total_remaining_cents | money }}
|
||||||
</strong>
|
</strong>
|
||||||
</article>
|
</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 '' }}">
|
||||||
|
{{ session.total_player_owes_cents | money }}
|
||||||
|
</strong>
|
||||||
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel results-panel results-panel-full">
|
<section class="panel results-panel results-panel-full">
|
||||||
@@ -53,6 +65,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Player</th>
|
<th>Player</th>
|
||||||
<th>Buy-in</th>
|
<th>Buy-in</th>
|
||||||
|
<th>Fronted</th>
|
||||||
<th>Cash-out</th>
|
<th>Cash-out</th>
|
||||||
<th>Net</th>
|
<th>Net</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
@@ -70,6 +83,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ entry.buy_in_cents | money }}</td>
|
<td>{{ entry.buy_in_cents | money }}</td>
|
||||||
|
<td>{{ entry.front_cents | money }}</td>
|
||||||
<td>{{ entry.cash_out_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 '' }}">
|
<td class="{{ 'positive' if entry.net_cents > 0 else 'negative' if entry.net_cents < 0 else '' }}">
|
||||||
{{ entry.net_cents | money }}
|
{{ entry.net_cents | money }}
|
||||||
|
|||||||
Reference in new issue
Block a user