add a date picker for games per hour page

This commit is contained in:
2025-06-04 11:30:21 +02:00
parent 4c0818f95f
commit 4d6cf88160
2 changed files with 115 additions and 40 deletions

View File

@@ -1,5 +1,7 @@
'use client'
import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import {
Card,
CardContent,
@@ -13,6 +15,11 @@ import {
ChartTooltip,
ChartTooltipContent,
} from '@/components/ui/chart'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
import {
Select,
SelectContent,
@@ -20,9 +27,12 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { cn } from '@/lib/utils'
import { api } from '@/trpc/react'
import { format } from 'date-fns'
import { CalendarIcon } from 'lucide-react'
import { useState } from 'react'
import { BarChart, CartesianGrid, XAxis, YAxis, Bar } from 'recharts'
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'
const chartConfig = {
count: {
@@ -35,10 +45,22 @@ type GroupByOption = 'hour' | 'day' | 'week' | 'month'
export function GamesPerHourChart() {
const [groupBy, setGroupBy] = useState<GroupByOption>('hour')
const [dateRange, setDateRange] = useState<
| {
from?: Date | undefined
to?: Date | undefined
}
| undefined
>({
from: undefined,
to: undefined,
})
// Fetch games data with the selected grouping
// Fetch games data with the selected grouping and date range
const [gamesData] = api.history.games_per_hour.useSuspenseQuery({
groupBy,
startDate: dateRange?.from?.toISOString(),
endDate: dateRange?.to?.toISOString(),
})
// Format the title and description based on the grouping
@@ -76,26 +98,71 @@ export function GamesPerHourChart() {
}
return (
<Card className="w-full">
<CardHeader className="flex flex-row items-center justify-between">
<Card className='w-full'>
<CardHeader className='flex flex-row items-center justify-between'>
<div>
<CardTitle>{getTitleText()}</CardTitle>
<CardDescription>Number of games played over time</CardDescription>
</div>
<Select value={groupBy} onValueChange={(value) => setGroupBy(value as GroupByOption)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select grouping" />
</SelectTrigger>
<SelectContent>
<SelectItem value="hour">Group by Hour</SelectItem>
<SelectItem value="day">Group by Day</SelectItem>
<SelectItem value="week">Group by Week</SelectItem>
<SelectItem value="month">Group by Month</SelectItem>
</SelectContent>
</Select>
<div className='flex gap-2'>
<Popover>
<PopoverTrigger asChild>
<Button
id='date'
variant={'outline'}
className={cn(
'w-[280px] justify-start text-left font-normal',
!dateRange?.from && 'text-muted-foreground'
)}
>
<CalendarIcon className='mr-2 h-4 w-4' />
{dateRange?.from ? (
dateRange.to ? (
<>
{format(dateRange.from, 'LLL dd, y')} -{' '}
{format(dateRange.to, 'LLL dd, y')}
</>
) : (
format(dateRange.from, 'LLL dd, y')
)
) : (
<span>Pick a date range</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className='w-auto p-0' align='end'>
<Calendar
initialFocus
mode='range'
defaultMonth={dateRange?.from}
selected={{
from: dateRange?.from,
to: dateRange?.to,
}}
onSelect={setDateRange}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
<Select
value={groupBy}
onValueChange={(value) => setGroupBy(value as GroupByOption)}
>
<SelectTrigger className='w-[180px]'>
<SelectValue placeholder='Select grouping' />
</SelectTrigger>
<SelectContent>
<SelectItem value='hour'>Group by Hour</SelectItem>
<SelectItem value='day'>Group by Day</SelectItem>
<SelectItem value='week'>Group by Week</SelectItem>
<SelectItem value='month'>Group by Month</SelectItem>
</SelectContent>
</Select>
</div>
</CardHeader>
<CardContent className="w-full h-[500px] p-2">
<ChartContainer config={chartConfig} className="w-full h-full">
<CardContent className='h-[500px] w-full p-2'>
<ChartContainer config={chartConfig} className='h-full w-full'>
<BarChart
data={gamesData}
margin={{
@@ -105,23 +172,21 @@ export function GamesPerHourChart() {
bottom: 60,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<CartesianGrid strokeDasharray='3 3' />
<XAxis
dataKey="timeUnit"
dataKey='timeUnit'
angle={-45}
textAnchor="end"
textAnchor='end'
height={60}
tickFormatter={formatXAxisTick}
/>
<YAxis />
<ChartTooltip
content={
<ChartTooltipContent
formatter={(value) => `${value} games`}
/>
<ChartTooltipContent formatter={(value) => `${value} games`} />
}
/>
<Bar dataKey="count" fill="var(--color-count)" />
<Bar dataKey='count' fill='var(--color-count)' />
</BarChart>
</ChartContainer>
</CardContent>

View File

@@ -1,43 +1,40 @@
import { createTRPCRouter, publicProcedure } from '@/server/api/trpc'
import { db } from '@/server/db'
import { metadata, player_games, raw_history } from '@/server/db/schema'
import { desc, eq } from 'drizzle-orm'
import { and, desc, eq, gt, lt, sql } from 'drizzle-orm'
import ky from 'ky'
import { chunk } from 'remeda'
import { z } from 'zod'
export const history_router = createTRPCRouter({
user_games: publicProcedure
.input(
z.object({
user_id: z.string(),
})
)
.query(async ({ ctx, input }) => {
return await ctx.db
.select()
.from(player_games)
.where(eq(player_games.playerId, input.user_id))
.orderBy(desc(player_games.gameNum))
}),
games_per_hour: publicProcedure
.input(
z
.object({
groupBy: z.enum(['hour', 'day', 'week', 'month']).default('hour'),
startDate: z.string().optional(),
endDate: z.string().optional(),
})
.optional()
)
.query(async ({ ctx, input }) => {
const groupBy = input?.groupBy || 'hour'
// Fetch all games with their gameNum to identify unique games
const startDate = input?.startDate ? new Date(input.startDate) : undefined
const endDate = input?.endDate ? new Date(input.endDate) : undefined
const nextDay = endDate ? new Date(endDate) : undefined
if (nextDay) nextDay.setDate(nextDay.getDate() + 1)
const games = await ctx.db
.select({
gameTime: player_games.gameTime,
gameNum: player_games.gameNum,
})
.from(player_games)
.where(
and(
startDate ? gt(player_games.gameTime, startDate) : undefined,
nextDay ? lt(player_games.gameTime, nextDay) : undefined
)
)
.orderBy(player_games.gameTime)
// Track unique game numbers to avoid counting the same game twice
@@ -97,6 +94,19 @@ export const history_router = createTRPCRouter({
sync: publicProcedure.mutation(async () => {
return syncHistory()
}),
user_games: publicProcedure
.input(
z.object({
user_id: z.string(),
})
)
.query(async ({ ctx, input }) => {
return await ctx.db
.select()
.from(player_games)
.where(eq(player_games.playerId, input.user_id))
.orderBy(desc(player_games.gameNum))
}),
})
export async function syncHistory() {