add in more admin info

This commit is contained in:
NotARoomba 2026-02-20 16:50:50 -05:00
parent 68043e888a
commit 9289d0bbe9
2 changed files with 101 additions and 1 deletions

View file

@ -16,7 +16,7 @@ import {
import { newsTable } from "../schemas/news";
import { projectActivityTable } from "../schemas/activity";
import { getUserFromSession } from "../lib/auth";
import { calculateScrapsFromHours, getUserScrapsBalance, TIER_MULTIPLIERS, DOLLARS_PER_HOUR } from "../lib/scraps";
import { calculateScrapsFromHours, getUserScrapsBalance, TIER_MULTIPLIERS, DOLLARS_PER_HOUR, SCRAPS_PER_DOLLAR } from "../lib/scraps";
import { payoutPendingScraps, getNextPayoutDate } from "../lib/scraps-payout";
import { syncSingleProject } from "../lib/hackatime-sync";
import { computeItemPricing, updateShopItemPricing } from "../lib/shop-pricing";
@ -183,6 +183,31 @@ admin.get("/stats", async ({ headers, status }) => {
? Math.round((totalTierCost / roundedTotalHours) * 100) / 100
: 0;
// Real dollar cost from shop fulfillment
const [luckWinOrders, consolationCount] = await Promise.all([
db
.select({
itemPrice: shopItemsTable.price,
})
.from(shopOrdersTable)
.innerJoin(shopItemsTable, eq(shopOrdersTable.shopItemId, shopItemsTable.id))
.where(eq(shopOrdersTable.orderType, "luck_win")),
db
.select({ count: sql<number>`count(*)` })
.from(shopOrdersTable)
.where(eq(shopOrdersTable.orderType, "consolation")),
]);
const luckWinDollarCost = luckWinOrders.reduce(
(sum, o) => sum + o.itemPrice / SCRAPS_PER_DOLLAR,
0,
);
const consolationDollarCost = Number(consolationCount[0]?.count || 0) * 2;
const totalRealCost = luckWinDollarCost + consolationDollarCost;
const realCostPerHour = roundedTotalHours > 0
? Math.round((totalRealCost / roundedTotalHours) * 100) / 100
: 0;
return {
totalUsers,
totalProjects,
@ -203,6 +228,14 @@ admin.get("/stats", async ({ headers, status }) => {
tierCostBreakdown,
totalTierCost: Math.round(totalTierCost * 100) / 100,
avgCostPerHour,
shopRealCost: {
luckWinItemsCost: Math.round(luckWinDollarCost * 100) / 100,
luckWinCount: luckWinOrders.length,
consolationShippingCost: Math.round(consolationDollarCost * 100) / 100,
consolationCount: Number(consolationCount[0]?.count || 0),
totalRealCost: Math.round(totalRealCost * 100) / 100,
realCostPerHour,
},
};
});

View file

@ -43,6 +43,15 @@
totalCost: number;
}
interface ShopRealCost {
luckWinItemsCost: number;
luckWinCount: number;
consolationShippingCost: number;
consolationCount: number;
totalRealCost: number;
realCostPerHour: number;
}
interface Stats {
totalUsers: number;
totalProjects: number;
@ -56,6 +65,7 @@
tierCostBreakdown?: TierCost[];
totalTierCost?: number;
avgCostPerHour?: number;
shopRealCost?: ShopRealCost;
}
interface PendingProject {
@ -516,6 +526,63 @@
</div>
</div>
{/if}
{#if stats.shopRealCost}
<h2 class="mt-10 mb-6 text-2xl font-bold">real fulfillment cost</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-red-500 bg-red-50 p-6">
<div
class="flex h-16 w-16 items-center justify-center rounded-full bg-red-600 text-white"
>
<DollarSign size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">real cost per hour</p>
<p class="text-4xl font-bold text-red-700">
${stats.shopRealCost.realCostPerHour.toFixed(2)}
</p>
<p class="text-xs text-gray-400">fulfillment cost ÷ shipped hours</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-red-500 bg-red-50 p-6">
<div
class="flex h-16 w-16 items-center justify-center rounded-full bg-red-600 text-white"
>
<DollarSign size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">total real cost</p>
<p class="text-4xl font-bold text-red-700">
${stats.shopRealCost.totalRealCost.toFixed(2)}
</p>
<p class="text-xs text-gray-400">items won + consolation shipping</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 win items</p>
<p class="text-4xl font-bold">${stats.shopRealCost.luckWinItemsCost.toFixed(2)}</p>
<p class="text-xs text-gray-400">{stats.shopRealCost.luckWinCount} items won</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 shipping</p>
<p class="text-4xl font-bold">${stats.shopRealCost.consolationShippingCost.toFixed(2)}</p>
<p class="text-xs text-gray-400">{stats.shopRealCost.consolationCount} consolations × $2</p>
</div>
</div>
</div>
{/if}
{/if}
</div>