final / src /components /Achievement.jsx
Yingtao-Zheng's picture
MERGE EVERYTHING from feature/integration-cleaned :((((((((((((((((((((((((((((((((((((((((((((((( so tired
2be007a
import React, { useState, useEffect } from 'react';
function Achievement() {
const [stats, setStats] = useState({
total_sessions: 0,
total_focus_time: 0,
avg_focus_score: 0,
streak_days: 0
});
const [systemStats, setSystemStats] = useState(null);
const [badges, setBadges] = useState([]);
const [loading, setLoading] = useState(true);
// Format total focus time for display.
const formatTime = (seconds) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
};
// Load summary statistics.
useEffect(() => {
fetch('/api/stats/summary')
.then(res => res.json())
.then(data => {
setStats(data);
calculateBadges(data);
setLoading(false);
})
.catch(err => {
console.error('Failed to load stats:', err);
setLoading(false);
});
}, []);
// Derive unlocked badges from summary statistics.
useEffect(() => {
const fetchSystem = () => {
fetch('/api/stats/system')
.then(res => res.json())
.then(data => setSystemStats(data))
.catch(() => setSystemStats(null));
};
fetchSystem();
const interval = setInterval(fetchSystem, 3000);
return () => clearInterval(interval);
}, []);
const calculateBadges = (data) => {
const earnedBadges = [];
// First-session badge
if (data.total_sessions >= 1) {
earnedBadges.push({
id: 'first-session',
name: 'First Step',
description: 'Complete your first focus session',
icon: '🎯',
unlocked: true
});
}
// 10-session badge
if (data.total_sessions >= 10) {
earnedBadges.push({
id: 'ten-sessions',
name: 'Getting Started',
description: 'Complete 10 focus sessions',
icon: '⭐',
unlocked: true
});
}
// 50-session badge
if (data.total_sessions >= 50) {
earnedBadges.push({
id: 'fifty-sessions',
name: 'Dedicated',
description: 'Complete 50 focus sessions',
icon: 'πŸ†',
unlocked: true
});
}
// Focus Master badge (average focus score > 80%)
if (data.avg_focus_score >= 0.8 && data.total_sessions >= 5) {
earnedBadges.push({
id: 'focus-master',
name: 'Focus Master',
description: 'Maintain 80%+ average focus score',
icon: '🧠',
unlocked: true
});
}
// Streak badges
if (data.streak_days >= 7) {
earnedBadges.push({
id: 'week-streak',
name: 'Week Warrior',
description: '7 day streak',
icon: 'πŸ”₯',
unlocked: true
});
}
if (data.streak_days >= 30) {
earnedBadges.push({
id: 'month-streak',
name: 'Month Master',
description: '30 day streak',
icon: 'πŸ’Ž',
unlocked: true
});
}
// Total focus time badge (10+ hours)
if (data.total_focus_time >= 36000) {
earnedBadges.push({
id: 'ten-hours',
name: 'Endurance',
description: '10+ hours total focus time',
icon: '⏱️',
unlocked: true
});
}
// Full badge catalog, including locked examples
const allBadges = [
{
id: 'first-session',
name: 'First Step',
description: 'Complete your first focus session',
icon: '🎯',
unlocked: data.total_sessions >= 1
},
{
id: 'ten-sessions',
name: 'Getting Started',
description: 'Complete 10 focus sessions',
icon: '⭐',
unlocked: data.total_sessions >= 10
},
{
id: 'fifty-sessions',
name: 'Dedicated',
description: 'Complete 50 focus sessions',
icon: 'πŸ†',
unlocked: data.total_sessions >= 50
},
{
id: 'focus-master',
name: 'Focus Master',
description: 'Maintain 80%+ average focus score',
icon: '🧠',
unlocked: data.avg_focus_score >= 0.8 && data.total_sessions >= 5
},
{
id: 'week-streak',
name: 'Week Warrior',
description: '7 day streak',
icon: 'πŸ”₯',
unlocked: data.streak_days >= 7
},
{
id: 'month-streak',
name: 'Month Master',
description: '30 day streak',
icon: 'πŸ’Ž',
unlocked: data.streak_days >= 30
},
{
id: 'ten-hours',
name: 'Endurance',
description: '10+ hours total focus time',
icon: '⏱️',
unlocked: data.total_focus_time >= 36000
},
{
id: 'hundred-sessions',
name: 'Centurion',
description: 'Complete 100 focus sessions',
icon: 'πŸ‘‘',
unlocked: data.total_sessions >= 100
}
];
setBadges(allBadges);
};
return (
<main id="page-c" className="page">
<h1 className="page-title">My Achievement</h1>
{loading ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#888' }}>
Loading stats...
</div>
) : (
<>
{systemStats && systemStats.cpu_percent != null && (
<div style={{
textAlign: 'center',
marginBottom: '12px',
padding: '8px 12px',
background: 'rgba(0,0,0,0.2)',
borderRadius: '8px',
fontSize: '13px',
color: '#aaa'
}}>
Server: CPU <strong style={{ color: '#8f8' }}>{systemStats.cpu_percent}%</strong>
{' Β· '}
RAM <strong style={{ color: '#8af' }}>{systemStats.memory_percent}%</strong>
{systemStats.memory_used_mb != null && ` (${systemStats.memory_used_mb}/${systemStats.memory_total_mb} MB)`}
</div>
)}
<div className="stats-grid">
<div className="stat-card">
<div className="stat-number" id="total-sessions">{stats.total_sessions}</div>
<div className="stat-label">Total Sessions</div>
</div>
<div className="stat-card">
<div className="stat-number" id="total-hours">{formatTime(stats.total_focus_time)}</div>
<div className="stat-label">Total Focus Time</div>
</div>
<div className="stat-card">
<div className="stat-number" id="avg-focus">{(stats.avg_focus_score * 100).toFixed(1)}%</div>
<div className="stat-label">Average Focus</div>
</div>
<div className="stat-card">
<div className="stat-number" id="current-streak">{stats.streak_days}</div>
<div className="stat-label">Day Streak</div>
</div>
</div>
<div className="achievements-section">
<h2>Badges</h2>
<div id="badges-container" className="badges-grid">
{badges.map(badge => (
<div
key={badge.id}
className={`badge ${badge.unlocked ? 'unlocked' : 'locked'}`}
style={{
padding: '20px',
textAlign: 'center',
border: '2px solid',
borderColor: badge.unlocked ? '#00FF00' : '#444',
borderRadius: '10px',
backgroundColor: badge.unlocked ? 'rgba(0, 255, 0, 0.1)' : 'rgba(68, 68, 68, 0.1)',
opacity: badge.unlocked ? 1 : 0.5,
transition: 'all 0.3s'
}}
>
<div style={{ fontSize: '48px', marginBottom: '10px' }}>
{badge.unlocked ? badge.icon : 'πŸ”’'}
</div>
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
{badge.name}
</div>
<div style={{ fontSize: '12px', color: '#888' }}>
{badge.description}
</div>
</div>
))}
</div>
</div>
</>
)}
</main>
);
}
export default Achievement;