mirror of
https://github.com/System-End/scraps.git
synced 2026-04-19 16:28:20 +00:00
actuall
This commit is contained in:
parent
6890adb44c
commit
8af711ef7e
3 changed files with 208 additions and 3 deletions
|
|
@ -39,5 +39,8 @@ export const config = {
|
|||
airtableUsersTableId: process.env.AIRTABLE_USERS_TABLE_ID!,
|
||||
|
||||
// YSWS
|
||||
fraudToken: process.env.FRAUD_TOKEN
|
||||
fraudToken: process.env.FRAUD_TOKEN,
|
||||
|
||||
// HCB
|
||||
hcbOrgSlug: 'ysws-scraps',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ admin.get("/stats", async ({ headers, status }) => {
|
|||
? Math.round((totalTierCost / roundedTotalHours) * 100) / 100
|
||||
: 0;
|
||||
|
||||
// Real dollar cost from shop fulfillment
|
||||
// Max dollar cost from shop fulfillment (all luck_win orders regardless of fulfillment)
|
||||
const [luckWinOrders, consolationCount] = await Promise.all([
|
||||
db
|
||||
.select({
|
||||
|
|
@ -223,6 +223,75 @@ admin.get("/stats", async ({ headers, status }) => {
|
|||
? Math.round((totalRealCost / roundedTotalHours) * 100) / 100
|
||||
: 0;
|
||||
|
||||
// Actual fulfillment cost (only fulfilled orders + upgrades consumed on fulfilled wins)
|
||||
const [fulfilledLuckWinOrders, fulfilledConsolationCount, fulfilledUpgrades] =
|
||||
await Promise.all([
|
||||
db
|
||||
.select({
|
||||
itemPrice: shopItemsTable.price,
|
||||
})
|
||||
.from(shopOrdersTable)
|
||||
.innerJoin(
|
||||
shopItemsTable,
|
||||
eq(shopOrdersTable.shopItemId, shopItemsTable.id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(shopOrdersTable.orderType, "luck_win"),
|
||||
eq(shopOrdersTable.isFulfilled, true),
|
||||
),
|
||||
),
|
||||
db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(shopOrdersTable)
|
||||
.where(
|
||||
and(
|
||||
eq(shopOrdersTable.orderType, "consolation"),
|
||||
eq(shopOrdersTable.isFulfilled, true),
|
||||
),
|
||||
),
|
||||
db.execute(
|
||||
sql`SELECT COALESCE(SUM(rsh.cost), 0) AS total_cost
|
||||
FROM refinery_spending_history rsh
|
||||
WHERE (rsh.user_id, rsh.shop_item_id) IN (
|
||||
SELECT DISTINCT so.user_id, so.shop_item_id
|
||||
FROM shop_orders so
|
||||
WHERE so.order_type = 'luck_win' AND so.is_fulfilled = true
|
||||
)`,
|
||||
),
|
||||
]);
|
||||
|
||||
const fulfilledLuckWinDollarCost = fulfilledLuckWinOrders.reduce(
|
||||
(sum, o) => sum + o.itemPrice / SCRAPS_PER_DOLLAR,
|
||||
0,
|
||||
);
|
||||
const fulfilledConsolationDollarCost =
|
||||
Number(fulfilledConsolationCount[0]?.count || 0) * 2;
|
||||
const fulfilledUpgradeDollarCost =
|
||||
Number((fulfilledUpgrades.rows[0] as { total_cost: string })?.total_cost || 0) /
|
||||
SCRAPS_PER_DOLLAR;
|
||||
const totalActualCost =
|
||||
fulfilledLuckWinDollarCost +
|
||||
fulfilledConsolationDollarCost +
|
||||
fulfilledUpgradeDollarCost;
|
||||
|
||||
// HCB bank balance
|
||||
let hcbBalanceCents = 0;
|
||||
if (config.hcbOrgSlug) {
|
||||
try {
|
||||
const hcbRes = await fetch(
|
||||
`https://hcb.hackclub.com/api/v3/organizations/${config.hcbOrgSlug}`,
|
||||
{ headers: { Accept: "application/json" } },
|
||||
);
|
||||
if (hcbRes.ok) {
|
||||
const hcbData = await hcbRes.json() as { balances?: { balance_cents?: number } };
|
||||
hcbBalanceCents = hcbData.balances?.balance_cents ?? 0;
|
||||
}
|
||||
} catch {
|
||||
// HCB unavailable, leave balance at 0
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalUsers,
|
||||
totalProjects,
|
||||
|
|
@ -251,6 +320,21 @@ admin.get("/stats", async ({ headers, status }) => {
|
|||
totalRealCost: Math.round(totalRealCost * 100) / 100,
|
||||
realCostPerHour,
|
||||
},
|
||||
shopActualCost: {
|
||||
hcbBalanceCents,
|
||||
hcbBalance: Math.round((hcbBalanceCents / 100) * 100) / 100,
|
||||
fulfilledLuckWinCost: Math.round(fulfilledLuckWinDollarCost * 100) / 100,
|
||||
fulfilledLuckWinCount: fulfilledLuckWinOrders.length,
|
||||
fulfilledConsolationCost:
|
||||
Math.round(fulfilledConsolationDollarCost * 100) / 100,
|
||||
fulfilledConsolationCount: Number(
|
||||
fulfilledConsolationCount[0]?.count || 0,
|
||||
),
|
||||
fulfilledUpgradeCost: Math.round(fulfilledUpgradeDollarCost * 100) / 100,
|
||||
totalActualCost: Math.round(totalActualCost * 100) / 100,
|
||||
remainingBudget:
|
||||
Math.round((hcbBalanceCents / 100 - totalActualCost) * 100) / 100,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,18 @@
|
|||
realCostPerHour: number;
|
||||
}
|
||||
|
||||
interface ShopActualCost {
|
||||
hcbBalanceCents: number;
|
||||
hcbBalance: number;
|
||||
fulfilledLuckWinCost: number;
|
||||
fulfilledLuckWinCount: number;
|
||||
fulfilledConsolationCost: number;
|
||||
fulfilledConsolationCount: number;
|
||||
fulfilledUpgradeCost: number;
|
||||
totalActualCost: number;
|
||||
remainingBudget: number;
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
totalUsers: number;
|
||||
totalProjects: number;
|
||||
|
|
@ -66,6 +78,7 @@
|
|||
totalTierCost?: number;
|
||||
avgCostPerHour?: number;
|
||||
shopRealCost?: ShopRealCost;
|
||||
shopActualCost?: ShopActualCost;
|
||||
}
|
||||
|
||||
interface PendingProject {
|
||||
|
|
@ -533,7 +546,7 @@
|
|||
{/if}
|
||||
|
||||
{#if stats.shopRealCost}
|
||||
<h2 class="mt-10 mb-6 text-2xl font-bold">real fulfillment cost</h2>
|
||||
<h2 class="mt-10 mb-6 text-2xl font-bold">max 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
|
||||
|
|
@ -592,6 +605,111 @@
|
|||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if stats.shopActualCost}
|
||||
<h2 class="mt-10 mb-6 text-2xl font-bold">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-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">hcb balance</p>
|
||||
<p class="text-4xl font-bold text-green-700">
|
||||
${stats.shopActualCost.hcbBalance.toFixed(2)}
|
||||
</p>
|
||||
<p class="text-xs text-gray-400">hack club bank account</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-4 rounded-2xl border-4 p-6 {stats.shopActualCost
|
||||
.remainingBudget >= 0
|
||||
? 'border-green-500 bg-green-50'
|
||||
: 'border-red-500 bg-red-50'}"
|
||||
>
|
||||
<div
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full text-white {stats
|
||||
.shopActualCost.remainingBudget >= 0
|
||||
? 'bg-green-600'
|
||||
: 'bg-red-600'}"
|
||||
>
|
||||
<DollarSign size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-bold text-gray-500">remaining budget</p>
|
||||
<p
|
||||
class="text-4xl font-bold {stats.shopActualCost.remainingBudget >= 0
|
||||
? 'text-green-700'
|
||||
: 'text-red-700'}"
|
||||
>
|
||||
${stats.shopActualCost.remainingBudget.toFixed(2)}
|
||||
</p>
|
||||
<p class="text-xs text-gray-400">hcb balance − actual fulfillment cost</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 actual cost</p>
|
||||
<p class="text-4xl font-bold text-red-700">
|
||||
${stats.shopActualCost.totalActualCost.toFixed(2)}
|
||||
</p>
|
||||
<p class="text-xs text-gray-400">fulfilled items + consolations + upgrades</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">fulfilled luck wins</p>
|
||||
<p class="text-4xl font-bold">
|
||||
${stats.shopActualCost.fulfilledLuckWinCost.toFixed(2)}
|
||||
</p>
|
||||
<p class="text-xs text-gray-400">
|
||||
{stats.shopActualCost.fulfilledLuckWinCount} items fulfilled
|
||||
</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">fulfilled consolations</p>
|
||||
<p class="text-4xl font-bold">
|
||||
${stats.shopActualCost.fulfilledConsolationCost.toFixed(2)}
|
||||
</p>
|
||||
<p class="text-xs text-gray-400">
|
||||
{stats.shopActualCost.fulfilledConsolationCount} consolations × $2
|
||||
</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">upgrades consumed</p>
|
||||
<p class="text-4xl font-bold">
|
||||
${stats.shopActualCost.fulfilledUpgradeCost.toFixed(2)}
|
||||
</p>
|
||||
<p class="text-xs text-gray-400">refinery spending on fulfilled wins</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue