The hand log answered "what happened in hand 12"; it couldn't answer "what does Alice owe". Each resolved pot now records a per-seat ledger (what that player put in, what they took down) alongside the payouts, and a Session Standings panel sums it into one row per player — paid in, won, net, sorted by who's up. The CSV export carries the same table, so one file covers both the hand-by-hand trail and the cash-out numbers. Keyed by seat id rather than name, so renaming or reseating a player doesn't split their totals. Hands recorded before this change have no ledger and are skipped rather than counted wrong. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
71 lines
2.6 KiB
JavaScript
71 lines
2.6 KiB
JavaScript
// Turns the in-memory hand history into a CSV file the dealer can download for a
|
|
// paper trail — a house manager or a disputed chip count needs something that
|
|
// outlives the browser tab, not just a scrollable panel in the sidebar.
|
|
const HandHistoryExport = {
|
|
csvField(value) {
|
|
const str = String(value ?? "");
|
|
return /[",\n]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str;
|
|
},
|
|
|
|
formatTimestamp(timestamp) {
|
|
try {
|
|
return new Date(timestamp).toLocaleString();
|
|
} catch {
|
|
return "";
|
|
}
|
|
},
|
|
|
|
toCsv(handHistory, standings = []) {
|
|
const rows = [["Hand #", "Time", "Pot", "Payouts"]];
|
|
// handHistory is stored newest-first; the export reads better oldest-first.
|
|
[...handHistory].reverse().forEach(entry => {
|
|
const payouts = (entry.payouts || [])
|
|
.map(p => `${p.label}: ${Money.format(p.amount)}`)
|
|
.join("; ");
|
|
rows.push([
|
|
entry.handNumber,
|
|
this.formatTimestamp(entry.timestamp),
|
|
Money.format(entry.pot),
|
|
payouts,
|
|
]);
|
|
});
|
|
|
|
// The settle-up numbers go in the same file as the hands they came from, so a
|
|
// dealer handing over one export isn't asked for a second one at cash-out.
|
|
if (standings.length > 0) {
|
|
rows.push([]);
|
|
rows.push(["Session Standings"]);
|
|
rows.push(["Player", "Paid In", "Won", "Net"]);
|
|
standings.forEach(row => {
|
|
rows.push([row.name, Money.format(row.in), Money.format(row.won), Money.formatSigned(row.net)]);
|
|
});
|
|
}
|
|
|
|
return rows.map(row => row.map(f => this.csvField(f)).join(",")).join("\r\n");
|
|
},
|
|
|
|
fileName() {
|
|
const now = new Date();
|
|
const pad = n => String(n).padStart(2, "0");
|
|
const stamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
|
|
return `holdem-hand-history-${stamp}.csv`;
|
|
},
|
|
|
|
download(handHistory, standings = []) {
|
|
const csv = this.toCsv(handHistory, standings);
|
|
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
link.download = this.fileName();
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
URL.revokeObjectURL(url);
|
|
},
|
|
};
|
|
|
|
if (typeof module !== "undefined" && module.exports) {
|
|
module.exports = HandHistoryExport;
|
|
}
|