From 87f793dd8eb55ce2436932e88fa5b602ca180cf3 Mon Sep 17 00:00:00 2001 From: Gerald Leban Date: Wed, 14 Oct 2026 21:27:30 +0200 Subject: [PATCH] fix(stats): sum all kit photos and remove 30-day timeframe metrics --- app/stats/services/stats_service.ts | 23 ++-- database/schema.ts | 30 +----- inertia/pages/stats.tsx | 155 ++++++--------------------- tests/functional/stats/index.spec.ts | 32 +++++- types/stats.ts | 2 - 5 files changed, 73 insertions(+), 169 deletions(-) diff --git a/app/stats/services/stats_service.ts b/app/stats/services/stats_service.ts index 8b7cdce..502ea57 100644 --- a/app/stats/services/stats_service.ts +++ b/app/stats/services/stats_service.ts @@ -8,8 +8,6 @@ export default class StatsService { totalBuilders, totalPhotos, totalLikes, - monthlyLikes, - monthlyActiveBuilders, gradeBreakdown, topBuilders, recentActivity, @@ -18,22 +16,17 @@ export default class StatsService { db.from('kits').countDistinct('did as total').first(), - db.from('kits').whereNotNull('photo_cid').count('* as total').first(), - - db.from('kit_likes').count('* as total').first(), - - db - .from('kit_likes') - .whereRaw('created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)') - .count('* as total') - .first(), - db .from('kits') - .countDistinct('did as total') - .whereRaw('indexed_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)') + .select( + db.raw( + 'COALESCE(SUM(COALESCE(JSON_LENGTH(photos), CASE WHEN photo_cid IS NOT NULL THEN 1 ELSE 0 END)), 0) as total' + ) + ) .first(), + db.from('kit_likes').count('* as total').first(), + db .from('kits') .select('grade') @@ -66,8 +59,6 @@ export default class StatsService { totalBuilders: Number(totalBuilders?.total ?? 0), totalPhotos: Number(totalPhotos?.total ?? 0), totalLikes: Number(totalLikes?.total ?? 0), - monthlyLikes: Number(monthlyLikes?.total ?? 0), - monthlyActiveBuilders: Number(monthlyActiveBuilders?.total ?? 0), gradeBreakdown: (gradeBreakdown as { grade: string; count: number }[]).map((r) => ({ grade: r.grade, count: Number(r.count), diff --git a/database/schema.ts b/database/schema.ts index e2e82cc..fdedc3f 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -51,24 +51,7 @@ export class KitLikeSchema extends BaseModel { } export class KitSchema extends BaseModel { - static $columns = [ - 'acquiredAt', - 'cid', - 'createdAt', - 'did', - 'grade', - 'id', - 'indexedAt', - 'manufacturer', - 'name', - 'photoCid', - 'photos', - 'recordCreatedAt', - 'scale', - 'status', - 'updatedAt', - 'uri', - ] as const + static $columns = ['acquiredAt', 'cid', 'createdAt', 'did', 'grade', 'id', 'indexedAt', 'manufacturer', 'name', 'photoCid', 'photos', 'recordCreatedAt', 'scale', 'status', 'updatedAt', 'uri'] as const $columns = KitSchema.$columns @column.dateTime() declare acquiredAt: DateTime | null @@ -127,16 +110,7 @@ export class OauthStateSchema extends BaseModel { } export class ProfileSchema extends BaseModel { - static $columns = [ - 'avatarUrl', - 'createdAt', - 'description', - 'did', - 'displayName', - 'handle', - 'indexedAt', - 'updatedAt', - ] as const + static $columns = ['avatarUrl', 'createdAt', 'description', 'did', 'displayName', 'handle', 'indexedAt', 'updatedAt'] as const $columns = ProfileSchema.$columns @column() declare avatarUrl: string | null diff --git a/inertia/pages/stats.tsx b/inertia/pages/stats.tsx index 4a091ac..c3f8aec 100644 --- a/inertia/pages/stats.tsx +++ b/inertia/pages/stats.tsx @@ -1,111 +1,53 @@ -import { useState } from 'react' import BaseLayout from '~/layouts/base' import { usePage } from '@inertiajs/react' import { Link } from '@adonisjs/inertia/react' import type { InertiaProps } from '~/types' -import { Package, Users, Camera, Heart, ArrowUp, ArrowDown, Calendar } from 'lucide-react' - -type Timeframe = 'alltime' | '30d' +import { Package, Users, Camera, Heart, Calendar } from 'lucide-react' type PageProps = InertiaProps<{ totalKits: number totalBuilders: number totalPhotos: number totalLikes: number - monthlyLikes: number - monthlyActiveBuilders: number recentActivity: { month: string; count: number }[] gradeBreakdown: { grade: string; count: number }[] topBuilders: { handle: string; count: number }[] }> export default function Stats() { - const { - totalKits, - totalBuilders, - totalPhotos, - totalLikes, - monthlyLikes, - recentActivity, - gradeBreakdown, - topBuilders, - monthlyActiveBuilders, - } = usePage().props - - const [timeframe, setTimeframe] = useState('alltime') + const { totalKits, totalBuilders, totalPhotos, totalLikes, gradeBreakdown, topBuilders } = + usePage().props - const lastMonth = recentActivity[recentActivity.length - 1] - const prevMonth = recentActivity[recentActivity.length - 2] - const lastMonthKits = lastMonth?.count ?? 0 - const prevMonthKits = prevMonth?.count ?? 1 - const kitsDelta = Math.round(((lastMonthKits - prevMonthKits) / prevMonthKits) * 100) - - const metrics = - timeframe === 'alltime' - ? [ - { - label: 'Kits', - icon: Package, - value: totalKits.toLocaleString(), - badge: { positive: true, text: 'all time' }, - subtext: 'kits logged', - }, - { - label: 'Builders', - icon: Users, - value: totalBuilders.toLocaleString(), - badge: { positive: true, text: 'all time' }, - subtext: 'registered', - }, - { - label: 'Photos', - icon: Camera, - value: totalPhotos.toLocaleString(), - badge: { positive: true, text: 'all time' }, - subtext: 'uploaded', - }, - { - label: 'Likes', - icon: Heart, - value: (totalLikes ?? 0).toLocaleString(), - badge: { positive: true, text: 'all time' }, - subtext: 'received', - }, - ] - : [ - { - label: 'Kits', - icon: Package, - value: lastMonthKits.toLocaleString(), - badge: { - positive: kitsDelta >= 0, - text: `${kitsDelta >= 0 ? '+' : ''}${kitsDelta}%` as const, - arrow: true, - }, - subtext: 'vs previous month', - }, - { - label: 'Builders', - icon: Users, - value: monthlyActiveBuilders.toLocaleString(), - badge: { positive: true, text: '30 days' }, - subtext: 'active', - }, - { - label: 'Photos', - icon: Camera, - value: totalPhotos.toLocaleString(), - badge: { positive: true, text: '30 days' }, - subtext: 'uploaded', - }, - { - label: 'Likes', - icon: Heart, - value: (monthlyLikes ?? 0).toLocaleString(), - badge: { positive: true, text: '30 days' }, - subtext: 'received', - }, - ] + const metrics = [ + { + label: 'Kits', + icon: Package, + value: totalKits.toLocaleString(), + badge: { positive: true, text: 'all time' }, + subtext: 'kits logged', + }, + { + label: 'Builders', + icon: Users, + value: totalBuilders.toLocaleString(), + badge: { positive: true, text: 'all time' }, + subtext: 'registered', + }, + { + label: 'Photos', + icon: Camera, + value: totalPhotos.toLocaleString(), + badge: { positive: true, text: 'all time' }, + subtext: 'uploaded', + }, + { + label: 'Likes', + icon: Heart, + value: (totalLikes ?? 0).toLocaleString(), + badge: { positive: true, text: 'all time' }, + subtext: 'received', + }, + ] const maxGrade = gradeBreakdown[0]?.count ?? 1 @@ -125,31 +67,6 @@ export default function Stats() { {/* Key metrics row */}

Key metrics

-
- {(['alltime', '30d'] as Timeframe[]).map((tf) => ( - - ))} -
{/* 4 KPI cards */} @@ -176,12 +93,6 @@ export default function Stats() { : 'bg-destructive text-destructive-foreground', ].join(' ')} > - {'arrow' in badge && - (badge.positive ? ( -