Skip to content

Commit

Permalink
feat: some more work on the leaderboard - pagination in the works
Browse files Browse the repository at this point in the history
  • Loading branch information
Shigbeard committed Aug 21, 2024
1 parent 5420f1a commit 2826935
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 48 deletions.
110 changes: 67 additions & 43 deletions app/components/ui/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
TableRow,
} from "~/components/ui/table"

import { Button } from "~/components/ui/button"

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
Expand All @@ -29,52 +31,74 @@ export function DataTable<TData, TValue>({
data,
columns,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
})

return (
<div className="rounded-md border">
<Table className={className}>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
<div>
<div className="rounded-md border border-secondary-foreground dark:border-primary-foreground ">
<Table className={className}>
<TableHeader className="dark:bg-secondary dark:hover:bg-secondary bg-primary hover:bg-primary dark:text-primary text-secondary ">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow className="dark:bg-secondary dark:hover:bg-secondary bg-primary hover:bg-primary dark:text-primary text-secondary" key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead scope="col" className="font-bold text-secondary dark:text-primary bg-primary dark:bg-secondary" key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No data available
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No data available
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
className="dark:bg-secondary dark:hover:bg-secondary bg-primary hover:bg-primary dark:text-primary text-secondary hover:text-blue-500"
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={false}
>
Previous
</Button>
<Button
className="dark:bg-secondary dark:hover:bg-secondary bg-primary hover:bg-primary dark:text-primary text-secondary hover:text-blue-500"
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={false}
>
Next
</Button>
</div>
</div>
)
}
16 changes: 14 additions & 2 deletions app/db/mgemod/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mysqlTable, mysqlSchema, AnyMySqlColumn, int, varchar, char, tinyint } from "drizzle-orm/mysql-core"
import { mysqlTable, mysqlSchema, AnyMySqlColumn, int, varchar, char, tinyint, mysqlView } from "drizzle-orm/mysql-core"
import { sql } from "drizzle-orm"

export const connections = mysqlTable("connections", {
Expand Down Expand Up @@ -50,6 +50,18 @@ export const mgemodStats = mysqlTable("mgemod_stats", {
hitblip: int("hitblip").notNull(),
});

export const leaderboard = mysqlView("leaderboard", {
id: int("id").autoincrement().notNull(),
rating: int("rating").notNull(),
steamid: varchar("steamid", { length: 32 }).notNull(),
name: varchar("name", { length: 64 }).notNull(),
wins: int("wins").notNull(),
losses: int("losses").notNull(),
lastplayed: int("lastplayed").notNull(),
hitblip: int("hitblip").notNull(),
rank: int("rank").notNull(),
}).existing();

export const vpnBlock = mysqlTable("VPNBlock", {
playername: char("playername", { length: 128 }).notNull(),
steamid: char("steamid", { length: 32 }).notNull(),
Expand All @@ -60,4 +72,4 @@ export const vpnBlock = mysqlTable("VPNBlock", {

export const vpnBlockWl = mysqlTable("VPNBlock_wl", {
steamid: char("steamid", { length: 32 }).notNull(),
});
});
44 changes: 41 additions & 3 deletions app/routes/leaderboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { Stats, StatsColumns } from "~/typedefs/leaderboards"
import { defer, useLoaderData, Await, type MetaFunction } from "@remix-run/react";
import { DataTable } from "~/components/ui/data-table"
import { drizzle } from "drizzle-orm/mysql2";
// import { count } from "drizzle-orm";
import mysql from "mysql2/promise";
import * as schema from "~/db/mgemod/schema";
import { themeSessionResolver } from "~/sessions.server";
import { LoaderFunctionArgs } from "@remix-run/node";
// import { gt } from "drizzle-orm";

export const meta: MetaFunction = () => {
return [
Expand All @@ -16,6 +18,24 @@ export const meta: MetaFunction = () => {

export async function loader ({ request }: LoaderFunctionArgs) {
const { getTheme } = await themeSessionResolver(request);
const url = new URL(request.url);
const search = url.searchParams;
const queryParams = {
limit: parseInt(search.get('l') ?? "10"),
page: parseInt(search.get('p') ?? "1"),
// age: parseInt(search.get('a') ?? "60"),
}

// guard rails
isNaN(queryParams.limit) && (queryParams.limit = 50);
isNaN(queryParams.page) && (queryParams.page = 1);
// isNaN(queryParams.age) && (queryParams.age = 60);

// const now = Date.now() / 1000;
// const age = now - (queryParams.age * 86400);

const offset = (queryParams.page - 1) * queryParams.limit;

const connection = await mysql.createConnection({
host: process.env.MGEMOD_DB_HOST,
user: process.env.MGEMOD_DB_USER,
Expand All @@ -24,9 +44,27 @@ export async function loader ({ request }: LoaderFunctionArgs) {
port: parseInt(process.env.MGEMOD_DB_PORT ?? "3306"),
});
const db = drizzle(connection, {schema, mode: 'default'});
const stats = await db.select().from(schema.mgemodStats).limit(10);
// sort by rating
stats.sort((a, b) => b.rating - a.rating);
const stats = await db.select({
id: schema.leaderboard.id,
rating: schema.leaderboard.rating,
steamid: schema.leaderboard.steamid,
name: schema.leaderboard.name,
wins: schema.leaderboard.wins,
losses: schema.leaderboard.losses,
lastplayed: schema.leaderboard.lastplayed,
hitblip: schema.leaderboard.hitblip,
rank: schema.leaderboard.rank,
}).from(schema.leaderboard)
// .where(gt(schema.leaderboard.lastplayed, age))
.offset(offset)
.limit(queryParams.limit);
if (stats.length == 0) {
connection.destroy();
return defer({
theme: getTheme(),
stats: [],
});
}
connection.destroy();
return defer({
theme: getTheme(),
Expand Down
3 changes: 3 additions & 0 deletions app/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ span.marquee {
}
}

.max-h-table {
height: 40rem;
}


@media (min-width: 768px) {
Expand Down
74 changes: 74 additions & 0 deletions app/typedefs/leaderboards.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
import { ColumnDef } from "@tanstack/react-table"
import { Button } from "~/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu"
import { MoreHorizontal } from "lucide-react"
import { steamidConvert64 } from "~/utils"

export type Duel = {
id: number
Expand All @@ -21,9 +32,15 @@ export type Stats = {
losses: number
lastplayed: number // Unix timestamp
hitblip: number // usually 1 but including all the same
rank: number
count: number
}

export const StatsColumns: ColumnDef<Stats>[] = [
{
accessorKey: "rank",
header: "Rank",
},
{
accessorKey: "name",
header: "Name",
Expand All @@ -47,5 +64,62 @@ export const StatsColumns: ColumnDef<Stats>[] = [
{
accessorKey: "lastplayed",
header: "Last Played",
cell: ({ row }) => {
const date = row.original.lastplayed
const now = Date.now() / 1000
let n = now - date
if (now - date < (60 * 60)) {
n = Math.floor(n / 60)
return `${n} minute${n > 1 ? 's' : ''} ago`
} else if (now - date < (60 * 60 * 24)) {
n = Math.floor(n / (60 * 60))
return `${n} hour${n > 1 ? 's' : ''} ago`
} else if (now - date < (60 * 60 * 24 * 7)) {
n = Math.floor(n / (60 * 60 * 24))
return `${n} day${n > 1 ? 's' : ''} ago`
} else if (now - date < (60 * 60 * 24 * 30)) {
n = Math.floor(n / (60 * 60 * 24 * 7))
return `${n} week${n > 1 ? 's' : ''} ago`
} else if (now - date < (60 * 60 * 24 * 365)) {
n = Math.floor(n / (60 * 60 * 24 * 30))
return `${n} month${n > 1 ? 's' : ''} ago`
}
n = Math.floor(n / (60 * 60 * 24 * 365))
return `${n} year${n > 1 ? 's' : ''} ago`
}
},
{
id: "actions",
cell: ({ row }) => {
const ranking = row.original
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(ranking.steamid)}
>
Copy SteamID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => {
// convert steamid to profile id
const steamid = steamidConvert64(ranking.steamid)
window.open(`https://steamcommunity.com/profiles/${steamid}`)
} }
>View Steam Profile</DropdownMenuItem>
{/* <DropdownMenuItem>View payment details</DropdownMenuItem> */}
</DropdownMenuContent>
</DropdownMenu>
)
}
}
]

9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"dependencies": {
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@remix-run/node": "^2.11.0",
Expand Down

0 comments on commit 2826935

Please sign in to comment.