generated from nhcarrigan/template
feat: add statistics panel with all-time, current run, and progress stats
This commit is contained in:
@@ -12,9 +12,10 @@ import { EquipmentPanel } from "./EquipmentPanel.js";
|
||||
import { OfflineModal } from "./OfflineModal.js";
|
||||
import { PrestigePanel } from "./PrestigePanel.js";
|
||||
import { QuestPanel } from "./QuestPanel.js";
|
||||
import { StatisticsPanel } from "./StatisticsPanel.js";
|
||||
import { UpgradePanel } from "./UpgradePanel.js";
|
||||
|
||||
type Tab = "adventurers" | "upgrades" | "quests" | "bosses" | "equipment" | "achievements" | "prestige";
|
||||
type Tab = "adventurers" | "upgrades" | "quests" | "bosses" | "equipment" | "achievements" | "prestige" | "statistics";
|
||||
|
||||
const TABS: { id: Tab; label: string }[] = [
|
||||
{ id: "adventurers", label: "⚔️ Adventurers" },
|
||||
@@ -24,6 +25,7 @@ const TABS: { id: Tab; label: string }[] = [
|
||||
{ id: "equipment", label: "🗡️ Equipment" },
|
||||
{ id: "achievements", label: "🏆 Achievements" },
|
||||
{ id: "prestige", label: "⭐ Prestige" },
|
||||
{ id: "statistics", label: "📊 Statistics" },
|
||||
];
|
||||
|
||||
export const GameLayout = (): React.JSX.Element => {
|
||||
@@ -98,6 +100,7 @@ export const GameLayout = (): React.JSX.Element => {
|
||||
{activeTab === "equipment" && <EquipmentPanel />}
|
||||
{activeTab === "achievements" && <AchievementPanel />}
|
||||
{activeTab === "prestige" && <PrestigePanel />}
|
||||
{activeTab === "statistics" && <StatisticsPanel />}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { useGame } from "../../context/GameContext.js";
|
||||
import { PRESTIGE_UPGRADES } from "../../data/prestigeUpgrades.js";
|
||||
|
||||
const formatDate = (timestamp: number): string =>
|
||||
new Date(timestamp).toLocaleDateString("en-GB", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
interface StatCardProps {
|
||||
icon: string;
|
||||
label: string;
|
||||
value: string;
|
||||
sub?: string;
|
||||
}
|
||||
|
||||
const StatCard = ({ icon, label, value, sub }: StatCardProps): React.JSX.Element => (
|
||||
<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 && <span className="profile-stat-date">{sub}</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const StatisticsPanel = (): React.JSX.Element => {
|
||||
const { state, formatNumber } = useGame();
|
||||
|
||||
if (!state) return <section className="panel"><p>Loading...</p></section>;
|
||||
|
||||
const { player, resources, prestige, bosses, quests, zones, adventurers, upgrades, equipment, achievements } = state;
|
||||
|
||||
const bossesDefeated = bosses.filter((b) => b.status === "defeated").length;
|
||||
const questsCompleted = quests.filter((q) => q.status === "completed").length;
|
||||
const zonesUnlocked = zones.filter((z) => z.status === "unlocked").length;
|
||||
const adventurersRecruited = adventurers.reduce((sum, a) => sum + a.count, 0);
|
||||
const equipmentOwned = (equipment ?? []).filter((e) => e.owned).length;
|
||||
const upgradesPurchased = upgrades.filter((u) => u.purchased).length;
|
||||
const achievementsUnlocked = (achievements ?? []).filter((a) => a.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"
|
||||
value={formatNumber(player.totalGoldEarned)}
|
||||
sub="across all runs"
|
||||
/>
|
||||
<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"
|
||||
value={`×${prestige.productionMultiplier.toFixed(2)}`}
|
||||
sub="from prestige"
|
||||
/>
|
||||
</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={formatNumber(resources.crystals)}
|
||||
/>
|
||||
<StatCard
|
||||
icon="🔮"
|
||||
label="Runestones"
|
||||
value={formatNumber(prestige.runestones)}
|
||||
sub="permanent currency"
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
@@ -1084,6 +1084,23 @@ body {
|
||||
background: var(--colour-accent-light);
|
||||
}
|
||||
|
||||
/* ===================== STATISTICS ===================== */
|
||||
.stats-section-header {
|
||||
border-bottom: 1px solid var(--colour-border);
|
||||
color: var(--colour-text-muted);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 0.75rem;
|
||||
margin-top: 1.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.statistics-panel .stats-section-header:first-of-type {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* ===================== ACHIEVEMENTS ===================== */
|
||||
.achievement-progress {
|
||||
color: var(--colour-text-muted);
|
||||
|
||||
Reference in New Issue
Block a user