mirror of
https://github.com/System-End/scraps.git
synced 2026-04-19 18:35: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!,
|
airtableUsersTableId: process.env.AIRTABLE_USERS_TABLE_ID!,
|
||||||
|
|
||||||
// YSWS
|
// 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
|
? Math.round((totalTierCost / roundedTotalHours) * 100) / 100
|
||||||
: 0;
|
: 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([
|
const [luckWinOrders, consolationCount] = await Promise.all([
|
||||||
db
|
db
|
||||||
.select({
|
.select({
|
||||||
|
|
@ -223,6 +223,75 @@ admin.get("/stats", async ({ headers, status }) => {
|
||||||
? Math.round((totalRealCost / roundedTotalHours) * 100) / 100
|
? Math.round((totalRealCost / roundedTotalHours) * 100) / 100
|
||||||
: 0;
|
: 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 {
|
return {
|
||||||
totalUsers,
|
totalUsers,
|
||||||
totalProjects,
|
totalProjects,
|
||||||
|
|
@ -251,6 +320,21 @@ admin.get("/stats", async ({ headers, status }) => {
|
||||||
totalRealCost: Math.round(totalRealCost * 100) / 100,
|
totalRealCost: Math.round(totalRealCost * 100) / 100,
|
||||||
realCostPerHour,
|
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;
|
realCostPerHour: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ShopActualCost {
|
||||||
|
hcbBalanceCents: number;
|
||||||
|
hcbBalance: number;
|
||||||
|
fulfilledLuckWinCost: number;
|
||||||
|
fulfilledLuckWinCount: number;
|
||||||
|
fulfilledConsolationCost: number;
|
||||||
|
fulfilledConsolationCount: number;
|
||||||
|
fulfilledUpgradeCost: number;
|
||||||
|
totalActualCost: number;
|
||||||
|
remainingBudget: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface Stats {
|
interface Stats {
|
||||||
totalUsers: number;
|
totalUsers: number;
|
||||||
totalProjects: number;
|
totalProjects: number;
|
||||||
|
|
@ -66,6 +78,7 @@
|
||||||
totalTierCost?: number;
|
totalTierCost?: number;
|
||||||
avgCostPerHour?: number;
|
avgCostPerHour?: number;
|
||||||
shopRealCost?: ShopRealCost;
|
shopRealCost?: ShopRealCost;
|
||||||
|
shopActualCost?: ShopActualCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PendingProject {
|
interface PendingProject {
|
||||||
|
|
@ -533,7 +546,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if stats.shopRealCost}
|
{#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="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 items-center gap-4 rounded-2xl border-4 border-red-500 bg-red-50 p-6">
|
||||||
<div
|
<div
|
||||||
|
|
@ -592,6 +605,111 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue