Spaces:
Sleeping
Sleeping
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; | |