diff --git a/client/src/components/StatsPanel.jsx b/client/src/components/StatsPanel.jsx new file mode 100644 index 0000000..eb7595c --- /dev/null +++ b/client/src/components/StatsPanel.jsx @@ -0,0 +1,216 @@ +import { useState, useEffect } from 'react'; +import './StatsPanel.css'; + +const StatsPanel = ({ selectedTurtle, apiUrl }) => { + const [miningStats, setMiningStats] = useState([]); + const [topMiners, setTopMiners] = useState([]); + const [timeFilter, setTimeFilter] = useState(7); // days + const [loading, setLoading] = useState(false); + + useEffect(() => { + loadMiningStats(); + loadTopMiners(); + + // Refresh stats every 30 seconds + const interval = setInterval(() => { + loadMiningStats(); + loadTopMiners(); + }, 30000); + + return () => clearInterval(interval); + }, [selectedTurtle, timeFilter]); + + const loadMiningStats = async () => { + setLoading(true); + try { + const url = selectedTurtle + ? `${apiUrl}/api/stats/mining/${selectedTurtle.turtleID}?days=${timeFilter}` + : `${apiUrl}/api/stats/mining?days=${timeFilter}`; + + const response = await fetch(url); + if (response.ok) { + const data = await response.json(); + setMiningStats(Array.isArray(data) ? data : [data]); + } + } catch (error) { + console.error('Failed to load mining stats:', error); + } finally { + setLoading(false); + } + }; + + const loadTopMiners = async () => { + try { + const response = await fetch(`${apiUrl}/api/stats/top-miners?limit=10`); + if (response.ok) { + const data = await response.json(); + setTopMiners(data); + } + } catch (error) { + console.error('Failed to load top miners:', error); + } + }; + + const getTotalBlocks = (stats) => { + if (!stats || !stats.blocks) return 0; + return stats.blocks.reduce((sum, block) => sum + block.count, 0); + }; + + const getBlockEmoji = (blockType) => { + if (!blockType) return '📦'; + const lower = blockType.toLowerCase(); + if (lower.includes('diamond')) return '💎'; + if (lower.includes('gold')) return '🟡'; + if (lower.includes('iron')) return '⚪'; + if (lower.includes('coal')) return '⚫'; + if (lower.includes('emerald')) return '🟢'; + if (lower.includes('redstone')) return '🔴'; + if (lower.includes('lapis')) return '🔵'; + if (lower.includes('copper')) return '🟠'; + if (lower.includes('stone')) return '🗿'; + if (lower.includes('dirt')) return '🟤'; + if (lower.includes('cobble')) return '🪨'; + if (lower.includes('wood') || lower.includes('log')) return '🪵'; + return '📦'; + }; + + const formatBlockType = (blockType) => { + if (!blockType) return 'Unknown'; + return blockType.replace('minecraft:', '').replace(/_/g, ' ') + .split(' ') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + }; + + return ( +
+
+

📊 Mining Statistics

+
+ + + + +
+
+ + {loading &&
Loading statistics...
} + + {/* Individual Turtle Stats */} + {selectedTurtle && miningStats.length > 0 && ( +
+

🐢 {selectedTurtle.name || `Turtle ${selectedTurtle.turtleID}`}

+ {miningStats.map((stat) => ( +
+
+
{getTotalBlocks(stat)}
+
Total Blocks Mined
+
+ + {stat.blocks && stat.blocks.length > 0 && ( +
+ {stat.blocks.map((block, idx) => ( +
+
{getBlockEmoji(block.blockType)}
+
+
{formatBlockType(block.blockType)}
+
{block.count} blocks
+
+
+ ))} +
+ )} +
+ ))} +
+ )} + + {/* Top Miners Leaderboard */} +
+

🏆 Top Miners

+
+ {topMiners.length === 0 && ( +
No mining data yet. Start mining!
+ )} + {topMiners.map((miner, idx) => ( +
+
+ {idx === 0 && '🥇'} + {idx === 1 && '🥈'} + {idx === 2 && '🥉'} + {idx > 2 && `#${idx + 1}`} +
+
+
Turtle {miner.turtleId}
+
+ {miner.totalBlocks} blocks • {miner.uniqueTypes} types +
+
+
{miner.totalBlocks}
+
+ ))} +
+
+ + {/* All Turtles Summary */} + {!selectedTurtle && miningStats.length > 0 && ( +
+

📈 All Turtles Overview

+
+ {miningStats.map((stat) => ( +
+
+ 🐢 Turtle {stat.turtleId} + {getTotalBlocks(stat)} +
+ {stat.blocks && stat.blocks.length > 0 && ( +
+ {stat.blocks.slice(0, 3).map((block, idx) => ( +
+ {getBlockEmoji(block.blockType)} + {block.count} +
+ ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Empty State */} + {!loading && miningStats.length === 0 && topMiners.length === 0 && ( +
+
⛏️
+
No Mining Data Yet
+
+ Start mining with your turtles to see statistics here! +
+
+ )} +
+ ); +}; + +export default StatsPanel;