This commit is contained in:
Nathan 2026-02-18 19:09:19 -05:00
parent 44048f3f03
commit 988b02c7d2
2 changed files with 110 additions and 3 deletions

View file

@ -80,15 +80,45 @@ admin.get('/stats', async ({ headers, status }) => {
const pendingWeightedGrants = Math.round(pendingHours / 10 * 100) / 100
const inProgressWeightedGrants = Math.round(inProgressHours / 10 * 100) / 100
// Shop spending stats
const [shopSpending, refinerySpending] = await Promise.all([
db.select({
totalSpent: sql<number>`COALESCE(SUM(${shopOrdersTable.totalPrice}), 0)`,
purchaseSpent: sql<number>`COALESCE(SUM(CASE WHEN ${shopOrdersTable.orderType} = 'purchase' THEN ${shopOrdersTable.totalPrice} ELSE 0 END), 0)`,
consolationSpent: sql<number>`COALESCE(SUM(CASE WHEN ${shopOrdersTable.orderType} = 'consolation' THEN ${shopOrdersTable.totalPrice} ELSE 0 END), 0)`,
luckWinSpent: sql<number>`COALESCE(SUM(CASE WHEN ${shopOrdersTable.orderType} = 'luck_win' THEN ${shopOrdersTable.totalPrice} ELSE 0 END), 0)`
}).from(shopOrdersTable),
db.select({
totalSpent: sql<number>`COALESCE(SUM(${refinerySpendingHistoryTable.cost}), 0)`
}).from(refinerySpendingHistoryTable)
])
const shopTotal = Number(shopSpending[0]?.totalSpent) || 0
const shopPurchases = Number(shopSpending[0]?.purchaseSpent) || 0
const shopConsolations = Number(shopSpending[0]?.consolationSpent) || 0
const shopLuckWins = Number(shopSpending[0]?.luckWinSpent) || 0
const refineryTotal = Number(refinerySpending[0]?.totalSpent) || 0
const totalScrapsSpent = shopTotal + refineryTotal
const roundedTotalHours = Math.round(totalHours * 10) / 10
const costPerHour = roundedTotalHours > 0 ? Math.round(totalScrapsSpent / roundedTotalHours * 100) / 100 : 0
return {
totalUsers,
totalProjects,
totalHours: Math.round(totalHours * 10) / 10,
totalHours: roundedTotalHours,
weightedGrants,
pendingHours: Math.round(pendingHours * 10) / 10,
pendingWeightedGrants,
inProgressHours: Math.round(inProgressHours * 10) / 10,
inProgressWeightedGrants
inProgressWeightedGrants,
shopStats: {
totalScrapsSpent,
shopPurchases,
shopConsolations,
shopLuckWins,
refineryUpgrades: refineryTotal,
costPerHour
}
}
})

View file

@ -1,11 +1,20 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { Users, FolderKanban, Clock, Scale, Hourglass, ShieldAlert, Coins, XCircle, Download, RefreshCw } from '@lucide/svelte';
import { Users, FolderKanban, Clock, Scale, Hourglass, ShieldAlert, Coins, XCircle, Download, RefreshCw, ShoppingCart, DollarSign } from '@lucide/svelte';
import { getUser } from '$lib/auth-client';
import { API_URL } from '$lib/config';
import { t } from '$lib/i18n';
interface ShopStats {
totalScrapsSpent: number;
shopPurchases: number;
shopConsolations: number;
shopLuckWins: number;
refineryUpgrades: number;
costPerHour: number;
}
interface Stats {
totalUsers: number;
totalProjects: number;
@ -15,6 +24,7 @@
pendingWeightedGrants: number;
inProgressHours: number;
inProgressWeightedGrants: number;
shopStats?: ShopStats;
}
interface PendingProject {
@ -323,6 +333,73 @@
</div>
</div>
</div>
{#if stats.shopStats}
<h2 class="mt-10 mb-6 text-2xl font-bold">shop spending</h2>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div class="flex items-center gap-4 rounded-2xl border-4 border-green-500 bg-green-50 p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-green-600 text-white">
<DollarSign size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">cost per hour</p>
<p class="text-4xl font-bold text-green-700">{stats.shopStats.costPerHour.toLocaleString()}</p>
<p class="text-xs text-gray-400">scraps spent ÷ shipped hours</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-green-500 bg-green-50 p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-green-600 text-white">
<ShoppingCart size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">total scraps spent</p>
<p class="text-4xl font-bold text-green-700">{stats.shopStats.totalScrapsSpent.toLocaleString()}</p>
<p class="text-xs text-gray-400">shop + refinery</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-black p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-black text-white">
<Coins size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">shop purchases</p>
<p class="text-4xl font-bold">{stats.shopStats.shopPurchases.toLocaleString()}</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-black p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-black text-white">
<Coins size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">consolation scraps</p>
<p class="text-4xl font-bold">{stats.shopStats.shopConsolations.toLocaleString()}</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-black p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-black text-white">
<Coins size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">luck wins</p>
<p class="text-4xl font-bold">{stats.shopStats.shopLuckWins.toLocaleString()}</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-black p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-black text-white">
<Coins size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">refinery upgrades</p>
<p class="text-4xl font-bold">{stats.shopStats.refineryUpgrades.toLocaleString()}</p>
</div>
</div>
</div>
{/if}
{/if}
</div>