351 lines
13 KiB
HTML
351 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ session.session_date }} · Poker Portal{% endblock %}
|
|
{% block content %}
|
|
<section class="hero-card compact">
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<div>
|
|
<p class="eyebrow">Session detail</p>
|
|
<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-self: center;">
|
|
{% if prev_session %}
|
|
<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_id=next_session.session_id) }}">
|
|
Next Session
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="grid payout-summary-grid">
|
|
<article class="panel stat-card">
|
|
<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">
|
|
<span>Total paid</span>
|
|
<strong class="positive">{{ session.total_paid_cents | money }}</strong>
|
|
</article>
|
|
|
|
<article class="panel stat-card">
|
|
<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>Players still owe</span>
|
|
<strong class="{{ 'negative' if session.total_player_owes_cents > 0 else '' }}">
|
|
{{ session.total_player_owes_cents | money }}
|
|
</strong>
|
|
</article>
|
|
|
|
<article class="panel stat-card">
|
|
<span>Written off</span>
|
|
<strong class="{{ 'negative' if session.total_front_writeoff_cents > 0 else '' }}">
|
|
{{ session.total_front_writeoff_cents | money }}
|
|
</strong>
|
|
</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">
|
|
<table class="data-table session-results-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Player</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 class="detail-col">Collected</th>
|
|
<th class="detail-col">Written off</th>
|
|
<th>Still owed</th>
|
|
<th>Status</th>
|
|
<th>Notes</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for entry in session.entries %}
|
|
<tr>
|
|
<td>
|
|
<a href="{{ url_for('player_detail', player_name=entry.player_name) }}">
|
|
{{ entry.player_name }}
|
|
</a>
|
|
</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.raw_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="detail-col">{{ entry.front_collected_applied_cents | money }}</td>
|
|
<td class="detail-col">{{ entry.front_writeoff_applied_cents | money }}</td>
|
|
|
|
<td class="{% if entry.player_owes_cents > 0 %}negative{% elif entry.front_writeoff_applied_cents > 0 %}negative{% elif entry.front_collected_applied_cents > 0 %}positive{% elif entry.current_due_cents > 0 %}negative{% else %}positive{% endif %}">
|
|
{% if entry.player_owes_cents > 0 %}
|
|
Owes {{ entry.player_owes_cents | money }}
|
|
{% elif entry.front_writeoff_applied_cents > 0 %}
|
|
Written off {{ entry.front_writeoff_applied_cents | money }}
|
|
{% elif entry.front_collected_applied_cents > 0 %}
|
|
Collected {{ entry.front_collected_applied_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>
|
|
{% if entry.notes %}
|
|
<ul class="note-list">
|
|
{% for note in entry.notes %}
|
|
<li>{{ note }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
—
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="grid two-col chart-row">
|
|
<div class="panel chart-panel chart-panel-compact">
|
|
<div class="panel-header">
|
|
<div>
|
|
<p class="eyebrow">Breakdown</p>
|
|
<h2>Net by player</h2>
|
|
</div>
|
|
</div>
|
|
<div class="chart-frame chart-frame-compact">
|
|
<canvas id="sessionBreakdownChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel session-insights-panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<p class="eyebrow">Quick read</p>
|
|
<h2>Session snapshot</h2>
|
|
</div>
|
|
</div>
|
|
|
|
{% set winners = session.entries | selectattr('net_cents', 'gt', 0) | list %}
|
|
{% set losers = session.entries | selectattr('net_cents', 'lt', 0) | list %}
|
|
|
|
<div class="detail-grid">
|
|
<div class="detail-row">
|
|
<span>Players</span>
|
|
<strong>{{ session.entries|length }}</strong>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span>Biggest winner</span>
|
|
<strong>
|
|
{% if winners %}
|
|
{{ winners | sort(attribute='net_cents', reverse=True) | first | attr('player_name') }}
|
|
{% else %}
|
|
—
|
|
{% endif %}
|
|
</strong>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span>Best result</span>
|
|
<strong class="positive">
|
|
{% if winners %}
|
|
{{ (winners | sort(attribute='net_cents', reverse=True) | first).net_cents | money }}
|
|
{% else %}
|
|
$0.00
|
|
{% endif %}
|
|
</strong>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span>Biggest loser</span>
|
|
<strong>
|
|
{% if losers %}
|
|
{{ losers | sort(attribute='net_cents') | first | attr('player_name') }}
|
|
{% else %}
|
|
—
|
|
{% endif %}
|
|
</strong>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span>Worst result</span>
|
|
<strong class="negative">
|
|
{% if losers %}
|
|
{{ (losers | sort(attribute='net_cents') | first).net_cents | money }}
|
|
{% else %}
|
|
$0.00
|
|
{% endif %}
|
|
</strong>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span>Unpaid total</span>
|
|
<strong class="{{ 'negative' if session.total_remaining_cents > 0 else 'positive' }}">
|
|
{{ session.total_remaining_cents | money }}
|
|
</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<p class="eyebrow">Audit trail</p>
|
|
<h2>Raw ledger events</h2>
|
|
</div>
|
|
</div>
|
|
<div class="event-feed">
|
|
{% for event in raw_events | reverse %}
|
|
<article class="event-card">
|
|
<div class="event-card-top">
|
|
<strong>{{ event.player_name or "Session" }}</strong>
|
|
<span class="pill pill-{{ event.event_type }}">{{ event.event_type }}</span>
|
|
</div>
|
|
<p class="event-amount">{{ event.amount_cents | money }}</p>
|
|
{% if event.note %}
|
|
<p class="muted-text small">{{ event.note }}</p>
|
|
{% endif %}
|
|
<p class="tiny-text">{{ event.created_at }} · by {{ event.actor }}</p>
|
|
</article>
|
|
{% else %}
|
|
<p class="muted-text">No raw events recorded.</p>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
|
|
<script>
|
|
const sessionChartData = {{ chart_data | tojson }};
|
|
const rootStyles = getComputedStyle(document.documentElement);
|
|
const textMuted = rootStyles.getPropertyValue('--text-muted').trim();
|
|
const lineColor = rootStyles.getPropertyValue('--line').trim();
|
|
|
|
const sessionBreakdownCtx = document.getElementById('sessionBreakdownChart');
|
|
if (sessionBreakdownCtx) {
|
|
new Chart(sessionBreakdownCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: sessionChartData.labels,
|
|
datasets: [{
|
|
label: 'Session Net',
|
|
data: sessionChartData.net_values,
|
|
backgroundColor: sessionChartData.net_colors,
|
|
borderColor: sessionChartData.net_colors,
|
|
borderRadius: 8,
|
|
borderSkipped: false,
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom',
|
|
labels: {
|
|
color: textMuted,
|
|
usePointStyle: true,
|
|
boxWidth: 10,
|
|
boxHeight: 10,
|
|
padding: 18,
|
|
},
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
ticks: { color: textMuted, maxRotation: 0 },
|
|
grid: { color: lineColor },
|
|
},
|
|
y: {
|
|
ticks: { color: textMuted },
|
|
grid: { color: lineColor },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
</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 %}
|