Files
gravl/frontend/src/pages/Dashboard.jsx
T
clawd 66812f9db2 Add ProfilePage and ProgressPage
ProfilePage:
- View/edit user info (name, age, height, goal, level)
- Show current measurements (weight, body fat, waist, neck)
- Show strength records (bench/squat/deadlift 1RM)

ProgressPage:
- Tab navigation (weight, body fat, strength)
- SVG line charts for progress visualization
- Stats showing current, first, and change
- Trend indicators (up/down)

Dashboard:
- Navigation icons for profile (👤) and progress (📊)
- Connected navigation to App.jsx routing
2026-02-01 11:50:52 +01:00

247 lines
8.3 KiB
React
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
import { useState, useEffect } from 'react'
import { useAuth } from '../context/AuthContext'
const API_URL = '/api'
// Coach greetings based on time and context
const getCoachGreeting = (user, todayWorkout) => {
const hour = new Date().getHours()
const name = user?.name?.split(' ')[0] || 'du'
if (hour < 10) {
return todayWorkout
? `Godmorgon ${name}! 💪 Redo för ${todayWorkout.name.toLowerCase()}?`
: `Godmorgon ${name}! Vilodag idag återhämtning är också träning.`
} else if (hour < 14) {
return todayWorkout
? `Dags att köra ${name}! ${todayWorkout.name} väntar.`
: `Lugn dag idag ${name}. Ladda batterierna! 🔋`
} else if (hour < 18) {
return todayWorkout
? `Eftermiddagspass? ${todayWorkout.name} står på schemat 🏋️`
: `Vila upp dig ${name}. Imorgon kör vi igen!`
} else {
return todayWorkout
? `Kvällspass ${name}? Perfekt för att släppa dagen.`
: `Bra jobbat denna veckan! Vila gott. 😴`
}
}
// Get weekday names
const weekdays = ['Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör', 'Sön']
function Dashboard({ onStartWorkout, onNavigate }) {
const { user, logout } = useAuth()
const [program, setProgram] = useState(null)
const [todayWorkout, setTodayWorkout] = useState(null)
const [loading, setLoading] = useState(true)
const [currentWeekStart, setCurrentWeekStart] = useState(getWeekStart(new Date()))
useEffect(() => {
fetchData()
}, [])
const fetchData = async () => {
try {
// Fetch user's program
const res = await fetch(`${API_URL}/programs/1`)
const data = await res.json()
setProgram(data)
// Determine today's workout based on day of week
const dayOfWeek = new Date().getDay() // 0 = Sunday
const adjustedDay = dayOfWeek === 0 ? 7 : dayOfWeek // Convert to 1-7 (Mon-Sun)
// Find if there's a workout scheduled for today
const todayDay = data.days?.find(d => d.day_number === adjustedDay)
setTodayWorkout(todayDay || null)
setLoading(false)
} catch (err) {
console.error('Failed to fetch data:', err)
setLoading(false)
}
}
if (loading) {
return (
<div className="dashboard loading">
<div className="spinner"></div>
<p>Laddar...</p>
</div>
)
}
const workoutDays = program?.days?.map(d => d.day_number) || []
return (
<div className="dashboard">
<header className="dashboard-header">
<div className="header-top">
<h1>🏋 Gravl</h1>
<nav className="nav-menu">
<button className="nav-btn active">Hem</button>
<button className="nav-btn" onClick={() => onNavigate('progress')}>📊</button>
<button className="nav-btn" onClick={() => onNavigate('profile')}>👤</button>
<button className="nav-btn logout" onClick={logout}></button>
</nav>
</div>
</header>
<main className="dashboard-main">
{/* Coach Greeting */}
<section className="coach-greeting">
<div className="coach-avatar">🧔</div>
<div className="coach-message">
<p>{getCoachGreeting(user, todayWorkout)}</p>
</div>
</section>
{/* Week Calendar */}
<section className="week-calendar">
<div className="calendar-header">
<button
className="calendar-nav"
onClick={() => setCurrentWeekStart(addDays(currentWeekStart, -7))}
>
</button>
<span className="calendar-title">
{formatWeekRange(currentWeekStart)}
</span>
<button
className="calendar-nav"
onClick={() => setCurrentWeekStart(addDays(currentWeekStart, 7))}
>
</button>
</div>
<div className="calendar-days">
{weekdays.map((name, idx) => {
const date = addDays(currentWeekStart, idx)
const dayNum = idx + 1
const isToday = isSameDay(date, new Date())
const hasWorkout = workoutDays.includes(dayNum)
const workout = program?.days?.find(d => d.day_number === dayNum)
return (
<div
key={idx}
className={`calendar-day ${isToday ? 'today' : ''} ${hasWorkout ? 'has-workout' : ''}`}
onClick={() => hasWorkout && workout && onStartWorkout(workout)}
>
<span className="day-name">{name}</span>
<span className="day-date">{date.getDate()}</span>
{hasWorkout && <span className="day-dot"></span>}
</div>
)
})}
</div>
</section>
{/* Today's Workout */}
<section className="todays-workout">
<h2>Dagens pass</h2>
{todayWorkout ? (
<div className="workout-card" onClick={() => onStartWorkout(todayWorkout)}>
<div className="workout-card-header">
<h3>{todayWorkout.name}</h3>
<span className="workout-duration">~45 min</span>
</div>
<div className="workout-exercises">
{todayWorkout.exercises?.filter(e => e.name).map((ex, i) => (
<div key={i} className="exercise-preview">
<span className="exercise-name">{ex.name}</span>
<span className="exercise-sets">{ex.sets}×{ex.reps_min}-{ex.reps_max}</span>
</div>
))}
</div>
<button className="start-workout-btn">
Starta pass
</button>
</div>
) : (
<div className="rest-day-card">
<div className="rest-icon">🧘</div>
<h3>Vilodag</h3>
<p>Inga pass schemalagda. Fokusera återhämtning!</p>
<div className="rest-tips">
<span>💤 Sömn</span>
<span>🥗 Näring</span>
<span>🚶 Lätt rörelse</span>
</div>
</div>
)}
</section>
{/* Quick Stats */}
<section className="quick-stats">
<div className="stat-card">
<span className="stat-value">{workoutDays.length}</span>
<span className="stat-label">Pass/vecka</span>
</div>
<div className="stat-card">
<span className="stat-value">2</span>
<span className="stat-label">Denna vecka</span>
</div>
<div className="stat-card">
<span className="stat-value">💪</span>
<span className="stat-label">Streak: 5</span>
</div>
</section>
{/* Upcoming Workouts */}
<section className="upcoming-workouts">
<h2>Kommande pass</h2>
<div className="upcoming-list">
{program?.days?.slice(0, 3).map((day, idx) => (
<div
key={day.id}
className="upcoming-item"
onClick={() => onStartWorkout(day)}
>
<span className="upcoming-day">{weekdays[day.day_number - 1]}</span>
<span className="upcoming-name">{day.name}</span>
<span className="upcoming-arrow"></span>
</div>
))}
</div>
</section>
</main>
</div>
)
}
// Helper functions
function getWeekStart(date) {
const d = new Date(date)
const day = d.getDay()
const diff = d.getDate() - day + (day === 0 ? -6 : 1)
return new Date(d.setDate(diff))
}
function addDays(date, days) {
const d = new Date(date)
d.setDate(d.getDate() + days)
return d
}
function isSameDay(d1, d2) {
return d1.getDate() === d2.getDate() &&
d1.getMonth() === d2.getMonth() &&
d1.getFullYear() === d2.getFullYear()
}
function formatWeekRange(weekStart) {
const end = addDays(weekStart, 6)
const startMonth = weekStart.toLocaleDateString('sv-SE', { month: 'short' })
const endMonth = end.toLocaleDateString('sv-SE', { month: 'short' })
if (startMonth === endMonth) {
return `${weekStart.getDate()} - ${end.getDate()} ${startMonth}`
}
return `${weekStart.getDate()} ${startMonth} - ${end.getDate()} ${endMonth}`
}
export default Dashboard