Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions sweepy-ui/src/components/LeaderboardTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { percentifyProbability, stringifyScore } from "../utils/format";
import SortableTable from "./SortableTable";

function LeaderboardTable({ data }) {
const allAssignments = data.participants
.flatMap((p) =>
p.assignments.map((a) => ({
probability: a.implied_probability,
player: a.name,
score: a.score,
participant: p.name,
})),
)
.sort((a, b) => a.score - b.score);

const columns = {
participant: true,
player: true,
probability: true,
score: data.tournament_id ? true : false,
};

const columnFormatters = {
probability: (val) => percentifyProbability(val),
score: (val) => stringifyScore(val),
};

console.log("Leaderboard data:", allAssignments);

return (
<SortableTable
sortableColumns={columns}
data={allAssignments}
columnFormatters={columnFormatters}
/>
);
}

export default LeaderboardTable;
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import React, { useState } from "react";

function SweepstakeTable({ data }) {
import { stringifyScore, percentifyProbability } from "../utils/format";

function ParticipantTable({ data }) {
const [expandedRow, setExpandedRow] = useState(null); // Track expanded row

const toggleRow = (index) => {
setExpandedRow(expandedRow === index ? null : index);
};

const stringifyScore = (score) => {
if (score === null || score === undefined) {
return "-";
} else if (score == 0) {
return "E";
} else if (score > 0) {
return `+${score}`;
} else {
return `${score}`;
}
};

const sortedParticipants = [...data.participants].sort((a, b) => {
return parseFloat(b.equity) - parseFloat(a.equity);
});
Expand All @@ -37,9 +26,7 @@ function SweepstakeTable({ data }) {
<React.Fragment key={index}>
<tr className="cursor-pointer" onClick={() => toggleRow(index)}>
<td className="px-4 py-2">{p.name}</td>
<td className="px-4 py-2">
{(parseFloat(p.equity) * 100).toFixed(2)}%
</td>
<td className="px-4 py-2">{percentifyProbability(p.equity)}</td>
</tr>

{/* Accordion for the expanded row */}
Expand Down Expand Up @@ -86,4 +73,4 @@ function SweepstakeTable({ data }) {
);
}

export default SweepstakeTable;
export default ParticipantTable;
73 changes: 73 additions & 0 deletions sweepy-ui/src/components/SortableTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useState } from "react";

const SortableTable = ({ sortableColumns, data, columnFormatters }) => {
// columns = { name: true, score: true, probability: false }
const [sortConfig, setSortConfig] = useState({ key: null, direction: "asc" });

const sortedData = React.useMemo(() => {
let sortableData = [...data];
if (sortConfig.key !== null && sortableColumns[sortConfig.key]) {
sortableData.sort((a, b) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === "asc" ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === "asc" ? 1 : -1;
}
return 0;
});
}
return sortableData;
}, [data, sortConfig, sortableColumns]);

const requestSort = (key) => {
if (!sortableColumns[key]) return; // ignore clicks if column not sortable
let direction = "asc";
if (sortConfig.key === key && sortConfig.direction === "asc") {
direction = "desc";
}
setSortConfig({ key, direction });
};

const getSortArrow = (key) => {
if (!sortableColumns[key]) return ""; // no arrow if not sortable
if (sortConfig.key !== key) return "↕";
return sortConfig.direction === "asc" ? "↑" : "↓";
};

return (
<table className="min-w-full border border-gray-300">
<thead className="bg-gray-100">
<tr>
{Object.keys(sortableColumns).map((colKey) => (
<th
key={colKey}
className={`px-4 py-2 text-left ${
sortableColumns[colKey] ? "cursor-pointer" : ""
}`}
onClick={() => requestSort(colKey)}
>
{colKey.charAt(0).toUpperCase() + colKey.slice(1)}{" "}
{getSortArrow(colKey)}
</th>
))}
</tr>
</thead>
<tbody>
{sortedData.map((row, idx) => (
<tr key={idx} className="border-t">
{Object.keys(sortableColumns).map((colKey) => (
<td key={colKey} className="px-4 py-2">
{columnFormatters[colKey]
? columnFormatters[colKey](row[colKey])
: row[colKey]}
</td>
))}
</tr>
))}
</tbody>
</table>
);
};

export default SortableTable;
11 changes: 6 additions & 5 deletions sweepy-ui/src/components/SweepstakeDetail.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState, useEffect } from "react";
import SweepstakeTable from "./SweepstakeTable";
import SweepstakeHistoryChart from "./SweepstakeHistoryChart";
import SweepstakeTableTabbed from "./SweepstakesTableTabbed";

const SECONDS_IN_MINUTE = 60;
const SECONDS_IN_HOUR = 3600;
Expand Down Expand Up @@ -84,7 +83,7 @@ function SweepstakeDetail({
}

return (
<div className="mt-6">
<div className="mt-6 w-auto">
<h2 className="text-lg font-semibold mb-2">
{sweepstake.name} (ID: {sweepstake.id})
</h2>
Expand All @@ -103,8 +102,10 @@ function SweepstakeDetail({
<p className="text-gray-600 mb-4">
Last refresh: {timeAgo(sweepstake.updated_at)}
</p>
<SweepstakeTable data={sweepstake} />
<SweepstakeHistoryChart sweepstakeHistory={sweepstakeHistory} />
<SweepstakeTableTabbed
sweepstake={sweepstake}
history={sweepstakeHistory}
/>
</div>
);
}
Expand Down
67 changes: 67 additions & 0 deletions sweepy-ui/src/components/SweepstakesTableTabbed.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useState } from "react";

import ParticipantTable from "./ParticipantTable";
import LeaderboardTable from "./LeaderboardTable";
import SweepstakeHistoryChart from "./SweepstakeHistoryChart";

const SweepstakeTableTabbed = ({ sweepstake, history }) => {
const [activeTab, setActiveTab] = useState("participant"); // Default to participant tab

const renderContent = () => {
switch (activeTab) {
case "participant":
return <ParticipantTable data={sweepstake} />;
case "history":
return <SweepstakeHistoryChart sweepstakeHistory={history} />;
case "leaderboard":
return <LeaderboardTable data={sweepstake} />;
default:
return null;
}
};

return (
<div className="w-full mx-auto">
{/* Tab Buttons */}
<div className="flex border-b">
<button
onClick={() => setActiveTab("participant")}
className={`px-4 py-2 ${
activeTab === "participant"
? "border-b-2 border-blue-500 font-semibold"
: ""
}`}
>
Participants
</button>
<button
onClick={() => setActiveTab("history")}
className={`px-4 py-2 ${
activeTab === "history"
? "border-b-2 border-blue-500 font-semibold"
: ""
}`}
>
History
</button>
{sweepstake.tournament_id && (
<button
onClick={() => setActiveTab("leaderboard")}
className={`px-4 py-2 ${
activeTab === "leaderboard"
? "border-b-2 border-blue-500 font-semibold"
: ""
}`}
>
Leaderboard
</button>
)}
</div>

{/* Tab Content */}
<div className="mt-4">{renderContent()}</div>
</div>
);
};

export default SweepstakeTableTabbed;
19 changes: 19 additions & 0 deletions sweepy-ui/src/utils/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function stringifyScore(score) {
if (score === null || score === undefined) {
return "-";
} else if (score === 0) {
return "E";
} else if (score > 0) {
return `+${score}`;
} else {
return `${score}`;
}
}

export function percentifyProbability(prob) {
if (prob === null || prob === undefined) {
return "-";
} else {
return `${(parseFloat(prob) * 100).toFixed(2)}%`;
}
}
2 changes: 1 addition & 1 deletion sweepy/generate_sweepstakes.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def refresh_sweepstake_odds(
Refresh the sweepstake by re-fetching the market data and updating the participants.
"""

latest_data = get_selections(client, sweepstake_db.market_id, False)
latest_data = get_selections(client, sweepstake_db.market_id)
fetched_at = datetime.datetime.now(datetime.timezone.utc)
if not latest_data:
raise MarketNotFoundException(
Expand Down
2 changes: 1 addition & 1 deletion sweepy/models/sweepstakes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class RunnerOdds(BaseModel):
provider_id: str
name: str
implied_probability: Decimal = condecimal(ge=0, le=1)
score: Decimal | None = None
score: int | None = None

def __le__(self, other: "RunnerOdds") -> bool:
return self.implied_probability <= other.implied_probability
Expand Down