Files
elysium/apps/web/src/components/game/statisticsPanel.tsx
T
hikari 1195b657a0
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m10s
CI / Lint, Build & Test (push) Successful in 1m13s
feat: another balance and bug fix pass (#238)
Working through open issues — fixes, balance changes, and features.

## Closed

- Closes #161
- Closes #181
- Closes #191
- Closes #199
- Closes #201
- Closes #202
- Closes #203
- Closes #204
- Closes #205
- Closes #206
- Closes #208
- Closes #211
- Closes #212
- Closes #213
- Closes #214
- Closes #216
- Closes #219
- Closes #220
- Closes #221
- Closes #222
- Closes #224
- Closes #225
- Closes #226
- Closes #228
- Closes #229
- Closes #230
- Closes #231
- Closes #232
- Closes #233
- Closes #234
- Closes #235
- Closes #236

 This PR was created with help from Hikari~ 🌸

Reviewed-on: #238
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
2026-04-06 18:17:00 -07:00

213 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file Statistics panel component showing player progress and all-time stats.
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable react/no-multi-comp -- Sub-component is tightly coupled to the panel */
/* eslint-disable max-lines-per-function -- Complex component with many render paths */
/* eslint-disable react/require-default-props -- TypeScript optional props with default parameters are sufficient */
import { useGame } from "../../context/gameContext.js";
import { PRESTIGE_UPGRADES } from "../../data/prestigeUpgrades.js";
import type { JSX } from "react";
const formatDate = (timestamp: number): string => {
return new Date(timestamp).toLocaleDateString("en-GB", {
day: "numeric",
month: "short",
year: "numeric",
});
};
interface StatCardProperties {
readonly icon: string;
readonly label: string;
readonly value: string;
readonly sub?: string | undefined;
}
/**
* Renders a single statistic card.
* @param props - The stat card properties.
* @param props.icon - The icon to display.
* @param props.label - The label for the stat.
* @param props.value - The value to display.
* @param props.sub - Optional sub-label.
* @returns The JSX element.
*/
const StatCard = ({
icon,
label,
value,
sub = undefined,
}: StatCardProperties): JSX.Element => {
return (
<div className="profile-stat">
<span className="profile-stat-icon">{icon}</span>
<span className="profile-stat-value">{value}</span>
<span className="profile-stat-label">{label}</span>
{sub === undefined
? null
: <span className="profile-stat-date">{sub}</span>
}
</div>
);
};
/**
* Renders the statistics panel with player progress and all-time stats.
* @returns The JSX element.
*/
const StatisticsPanel = (): JSX.Element => {
const { state, formatInteger, formatNumber } = useGame();
if (state === null) {
return (
<section className="panel">
<p>{"Loading..."}</p>
</section>
);
}
const {
player,
resources,
prestige,
bosses,
quests,
zones,
adventurers,
upgrades,
equipment,
achievements,
} = state;
const bossesDefeated = bosses.filter((boss) => {
return boss.status === "defeated";
}).length;
const questsCompleted = quests.filter((quest) => {
return quest.status === "completed";
}).length;
const zonesUnlocked = zones.filter((zone) => {
return zone.status === "unlocked";
}).length;
const adventurersRecruited = adventurers.reduce((sum, adventurer) => {
return sum + adventurer.count;
}, 0);
const equipmentOwned = equipment.filter((item) => {
return item.owned;
}).length;
const upgradesPurchased = upgrades.filter((upgrade) => {
return upgrade.purchased;
}).length;
const achievementsUnlocked = achievements.filter((achievement) => {
return achievement.unlockedAt !== null;
}).length;
const prestigeUpgradesPurchased = prestige.purchasedUpgradeIds.length;
return (
<section className="panel statistics-panel">
<h2>{"📊 Statistics"}</h2>
<h3 className="stats-section-header">{"All-Time"}</h3>
<div className="profile-stats">
<StatCard
icon="🪙"
label="Total Gold Earned"
sub="across all runs"
value={formatNumber(player.totalGoldEarned)}
/>
<StatCard
icon="👆"
label="Total Clicks"
value={formatNumber(player.totalClicks)}
/>
<StatCard icon="⭐" label="Prestiges" value={String(prestige.count)} />
<StatCard
icon="📅"
label="Guild Founded"
value={formatDate(player.createdAt)}
/>
<StatCard
icon="☁️"
label="Last Cloud Save"
value={formatDate(player.lastSavedAt)}
/>
<StatCard
icon="✖️"
label="Production Multiplier"
sub="from prestige"
value={`×${prestige.productionMultiplier.toFixed(2)}`}
/>
</div>
<h3 className="stats-section-header">{"Current Run"}</h3>
<div className="profile-stats">
<StatCard icon="🪙" label="Gold" value={formatNumber(resources.gold)} />
<StatCard
icon="✨"
label="Essence"
value={formatNumber(resources.essence)}
/>
<StatCard
icon="💎"
label="Crystals"
value={formatInteger(resources.crystals)}
/>
<StatCard
icon="🔮"
label="Runestones"
sub="permanent currency"
value={formatInteger(prestige.runestones)}
/>
</div>
<h3 className="stats-section-header">{"Progress"}</h3>
<div className="profile-stats">
<StatCard
icon="👹"
label="Bosses Defeated"
value={`${String(bossesDefeated)} / ${String(bosses.length)}`}
/>
<StatCard
icon="📜"
label="Quests Completed"
value={`${String(questsCompleted)} / ${String(quests.length)}`}
/>
<StatCard
icon="🗺️"
label="Zones Unlocked"
value={`${String(zonesUnlocked)} / ${String(zones.length)}`}
/>
<StatCard
icon="⚔️"
label="Adventurers Recruited"
value={formatNumber(adventurersRecruited)}
/>
<StatCard
icon="🗡️"
label="Equipment Owned"
value={`${String(equipmentOwned)} / ${String(equipment.length)}`}
/>
<StatCard
icon="🔧"
label="Upgrades Purchased"
value={`${String(upgradesPurchased)} / ${String(upgrades.length)}`}
/>
<StatCard
icon="🏆"
label="Achievements"
value={`${String(achievementsUnlocked)} / ${String(achievements.length)}`}
/>
<StatCard
icon="🔮"
label="Prestige Upgrades"
value={`${String(prestigeUpgradesPurchased)} / ${String(PRESTIGE_UPGRADES.length)}`}
/>
</div>
</section>
);
};
export { StatisticsPanel };