343 lines
13 KiB
HTML
343 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Admin - Poker Portal{% endblock %}
|
|
{% block content %}
|
|
<section class="hero-card compact">
|
|
<div>
|
|
<p class="eyebrow">Admin</p>
|
|
<h1>Ledger controls</h1>
|
|
<p class="muted-text">Append new buy-ins, cash-outs, corrections, and note-only events to the audit trail.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="grid two-col admin-layout">
|
|
<form class="panel form-card" method="post">
|
|
<p class="eyebrow">New event</p>
|
|
<h2>Add to the ledger</h2>
|
|
|
|
<label>
|
|
<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>
|
|
<span>Player name</span>
|
|
<input list="player_names" type="text" name="player_name" placeholder="Peter Venkman" required>
|
|
<datalist id="player_names">
|
|
{% for player_name in player_names %}
|
|
<option value="{{ player_name }}"></option>
|
|
{% endfor %}
|
|
</datalist>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Event type</span>
|
|
<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>
|
|
|
|
<label>
|
|
<span>Amount</span>
|
|
<input type="number" step="0.01" name="amount" placeholder="5.00">
|
|
</label>
|
|
|
|
<label>
|
|
<span>Note</span>
|
|
<textarea name="note" rows="4" placeholder="Rebuy, fronted chips, correction, partial payout, e-transfer sent, cash paid, etc."></textarea>
|
|
</label>
|
|
|
|
<button class="primary-button" type="submit">Add event</button>
|
|
</form>
|
|
|
|
<section class="grid">
|
|
<form class="panel form-card" method="post" action="{{ url_for('admin_open_session') }}">
|
|
<p class="eyebrow">Session state</p>
|
|
<h2>Open a new session</h2>
|
|
|
|
<label>
|
|
<span>Session date</span>
|
|
<input type="date" name="session_date" required>
|
|
</label>
|
|
|
|
<div class="button-row">
|
|
<button class="primary-button" type="submit">Open session</button>
|
|
</div>
|
|
</form>
|
|
|
|
<section class="panel form-card">
|
|
<div class="panel-header">
|
|
<div>
|
|
<p class="eyebrow">Tools</p>
|
|
<h2>Audit CSV</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-tools-grid">
|
|
<div class="helper-box">
|
|
<p class="muted-text">
|
|
Export the current audit trail or import a replacement CSV. Import creates a timestamped backup first.
|
|
</p>
|
|
<div class="button-row">
|
|
<a class="primary-button" href="{{ url_for('admin_export_csv') }}">Export CSV</a>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" action="{{ url_for('admin_import_csv') }}" enctype="multipart/form-data" class="helper-box import-form">
|
|
<label for="csv_file">
|
|
<span>Import replacement CSV</span>
|
|
</label>
|
|
<input id="csv_file" name="csv_file" type="file" accept=".csv,text/csv" required>
|
|
<div class="button-row">
|
|
<button
|
|
class="secondary-button"
|
|
type="submit"
|
|
onclick="return confirm('Replace the current audit CSV? A backup will be created first.');"
|
|
>
|
|
Import CSV
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
|
|
</section>
|
|
</section>
|
|
|
|
<section class="panel debt-panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<p class="eyebrow">Front debt</p>
|
|
<h2>Debt resolution</h2>
|
|
</div>
|
|
<div class="debt-summary">
|
|
<span>Open {{ admin_totals.players_owe_cents | money }}</span>
|
|
<span>Collected {{ admin_totals.collected_cents | money }}</span>
|
|
<span>Written off {{ admin_totals.written_off_cents | money }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{% if debt_entries %}
|
|
<div class="debt-action-grid">
|
|
<form class="form-card compact-form" method="post" action="{{ url_for('admin_collect_front') }}">
|
|
<h3>Collect debt</h3>
|
|
|
|
<label>
|
|
<span>Outstanding debt</span>
|
|
<select name="debt_key" required>
|
|
<option value="">Select debt</option>
|
|
{% for item in debt_entries %}
|
|
{% set session = item.session %}
|
|
{% set entry = item.entry %}
|
|
<option value="{{ session.session_id }}||{{ entry.player_name }}">
|
|
{{ entry.player_name }} · {{ session_label(session) }} · owes {{ entry.player_owes_cents | money }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Amount collected</span>
|
|
<input type="number" step="0.01" name="amount" placeholder="5.00" required>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Note</span>
|
|
<textarea name="note" rows="3" placeholder="E-transfer received, cash collected, etc."></textarea>
|
|
</label>
|
|
|
|
<div class="button-row">
|
|
<button class="primary-button" type="submit">Mark collected</button>
|
|
</div>
|
|
</form>
|
|
|
|
<form class="form-card compact-form" method="post" action="{{ url_for('admin_write_off_front') }}">
|
|
<h3>Write off debt</h3>
|
|
|
|
<label>
|
|
<span>Outstanding debt</span>
|
|
<select name="debt_key" required>
|
|
<option value="">Select debt</option>
|
|
{% for item in debt_entries %}
|
|
{% set session = item.session %}
|
|
{% set entry = item.entry %}
|
|
<option value="{{ session.session_id }}||{{ entry.player_name }}">
|
|
{{ entry.player_name }} · {{ session_label(session) }} · owes {{ entry.player_owes_cents | money }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Amount to write off</span>
|
|
<input type="number" step="0.01" name="amount" placeholder="5.00" required>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Note</span>
|
|
<textarea name="note" rows="3" placeholder="Won't be collected."></textarea>
|
|
</label>
|
|
|
|
<div class="button-row">
|
|
<button class="secondary-button" type="submit">Write off front</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{% else %}
|
|
<p class="muted-text">No outstanding front debt.</p>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<section class="grid admin-cash-grid">
|
|
<article class="panel stat-card">
|
|
<span>Cash in</span>
|
|
<strong>{{ admin_totals.cash_in_cents | money }}</strong>
|
|
</article>
|
|
|
|
<article class="panel stat-card">
|
|
<span>Paid out</span>
|
|
<strong>{{ admin_totals.paid_out_cents | money }}</strong>
|
|
</article>
|
|
|
|
<article class="panel stat-card">
|
|
<span>Still owed</span>
|
|
<strong class="{{ 'negative' if admin_totals.still_owed_cents > 0 else 'positive' }}">
|
|
{{ admin_totals.still_owed_cents | money }}
|
|
</strong>
|
|
</article>
|
|
|
|
<article class="panel stat-card">
|
|
<span>Players owe</span>
|
|
<strong class="{{ 'negative' if admin_totals.players_owe_cents > 0 else '' }}">
|
|
{{ admin_totals.players_owe_cents | money }}
|
|
</strong>
|
|
</article>
|
|
|
|
<article class="panel stat-card">
|
|
<span>Written off</span>
|
|
<strong class="{{ 'negative' if admin_totals.written_off_cents > 0 else '' }}">
|
|
{{ admin_totals.written_off_cents | money }}
|
|
</strong>
|
|
</article>
|
|
|
|
<article class="panel stat-card">
|
|
<span>Open balance</span>
|
|
<strong class="{% if admin_totals.open_balance_cents > 0 %}negative{% elif admin_totals.open_balance_cents < 0 %}positive{% endif %}">
|
|
{{ admin_totals.open_balance_cents | money }}
|
|
</strong>
|
|
</article>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<p class="eyebrow">Recent sessions</p>
|
|
<h2>Current totals</h2>
|
|
</div>
|
|
</div>
|
|
<div class="table-wrap small-table">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Session</th>
|
|
<th>Players</th>
|
|
<th>Cash in</th>
|
|
<th>Paid out</th>
|
|
<th>Still owed</th>
|
|
<th>Players owe</th>
|
|
<th>Collected</th>
|
|
<th>Written off</th>
|
|
<th>Open balance</th>
|
|
<th>Status</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for session in recent_sessions %}
|
|
<tr>
|
|
<td>
|
|
<a href="{{ url_for('session_detail', session_id=session.session_id) }}">
|
|
{{ session_label(session) }}
|
|
</a>
|
|
</td>
|
|
<td>{{ session.entries|length }}</td>
|
|
<td>{{ session.total_cash_in_cents | money }}</td>
|
|
<td>{{ session.total_paid_out_cents | money }}</td>
|
|
<td class="{{ 'negative' if session.total_current_due_cents > 0 else 'positive' }}">
|
|
{{ session.total_current_due_cents | money }}
|
|
</td>
|
|
<td class="{{ 'negative' if session.total_player_owes_cents > 0 else '' }}">
|
|
{{ session.total_player_owes_cents | money }}
|
|
</td>
|
|
<td class="{{ 'positive' if session.total_front_collected_cents > 0 else '' }}">
|
|
{{ session.total_front_collected_cents | money }}
|
|
</td>
|
|
<td class="{{ 'negative' if session.total_front_writeoff_cents > 0 else '' }}">
|
|
{{ session.total_front_writeoff_cents | money }}
|
|
</td>
|
|
<td class="{% if session.total_open_balance_cents > 0 %}negative{% elif session.total_open_balance_cents < 0 %}positive{% endif %}">
|
|
{{ session.total_open_balance_cents | money }}
|
|
</td>
|
|
<td>
|
|
<span class="status-badge status-{{ session.status }}">
|
|
{{ session.status }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<form method="post" action="{{ url_for('admin_session_state') }}" class="inline-action-form">
|
|
<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 %}
|
|
<button class="table-action-button" type="submit" name="state" value="open">Reopen</button>
|
|
{% endif %}
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="11">No sessions yet.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<div class="panel-header">
|
|
<div>
|
|
<p class="eyebrow">Recent raw events</p>
|
|
<h2>Ledger feed</h2>
|
|
</div>
|
|
</div>
|
|
<div class="event-feed condensed">
|
|
{% for event in recent_events %}
|
|
<article class="event-card">
|
|
<div class="event-card-top">
|
|
<strong>{{ event.player_name }}</strong>
|
|
<span class="pill pill-{{ event.event_type }}">{{ event.event_type }}</span>
|
|
</div>
|
|
<p class="event-amount">{{ event.amount_cents | money }}</p>
|
|
<p class="muted-text small">{{ event.session_date | pretty_date }}</p>
|
|
{% if event.note %}<p class="muted-text small">{{ event.note }}</p>{% endif %}
|
|
</article>
|
|
{% else %}
|
|
<p class="muted-text">No recent events.</p>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endblock %}
|