Files
gravl/frontend/src/pages/Dashboard.jsx
T
clawd 83ccd6c601 feat(05-03): Exercise research frontend integration
- Add ExerciseResearchPanel component with Get Research button, loading state, summary display, and source links
- Add ExerciseEncyclopediaPage with exercise list and integrated research panel
- Wire encyclopedia view into App.jsx navigation
- Add encyclopedia nav button to Dashboard
- Add CSS for research panel and encyclopedia search

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 19:20:40 +01:00

252 lines
8.7 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
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'
import { Icon, getActivityIconName } from '../components/Icons'
import Logo from '../components/Logo'
const API_URL = '/api'
// Coach greetings based on context
const getCoachGreeting = (user, todayWorkout) => {
const hour = new Date().getHours()
const name = user?.name?.split(' ')[0] || 'du'
if (todayWorkout) {
// There's a workout today
if (hour < 10) {
return `Godmorgon ${name}! Idag kör vi ${todayWorkout.name.toLowerCase()}. Redo?`
} else if (hour < 14) {
return `${todayWorkout.name} står på schemat idag. Dags att köra!`
} else if (hour < 18) {
return `Eftermiddagspass? ${todayWorkout.name} väntar på dig.`
} else {
return `Kvällspass ${name}? ${todayWorkout.name} perfekt för att avsluta dagen.`
}
} else {
// Rest day
if (hour < 10) {
return `Godmorgon ${name}! Vilodag idag perfekt för återhämtning.`
} else if (hour < 14) {
return `Ingen träning schemalagd. Ta en promenad eller stretcha lite?`
} else if (hour < 18) {
return `Vila är också träning! Lätt rörelse eller mobilitet idag?`
} else {
return `Lugn kväll ${name}. Ladda batterierna till nästa pass!`
}
}
}
// Rest day tips
const restDayTips = [
{ iconName: 'walking', text: 'Promenad' },
{ iconName: 'yoga', text: 'Stretching' },
{ iconName: 'swimming', text: 'Simning' },
{ iconName: 'cycling', text: 'Cykling' },
]
// 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 {
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()
const adjustedDay = dayOfWeek === 0 ? 7 : dayOfWeek
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 className="brand-title">
<Logo />
<span className="brand-name">Gravl</span>
</h1>
<nav className="nav-menu">
<button className="nav-btn active"><Icon name="home" size={18} /></button>
<button className="nav-btn" onClick={() => onNavigate('progress')}><Icon name="chart" size={18} /></button>
<button className="nav-btn" onClick={() => onNavigate('encyclopedia')} title="Exercise Encyclopedia"><Icon name="search" size={18} /></button>
<button className="nav-btn" onClick={() => onNavigate('profile')}><Icon name="user" size={18} /></button>
<button className="nav-btn logout" onClick={logout}><Icon name="logout" size={18} /></button>
</nav>
</div>
</header>
<main className="dashboard-main">
{/* Week Calendar - TOP */}
<section className="week-calendar">
<div className="calendar-header">
<button
className="calendar-nav"
onClick={() => setCurrentWeekStart(addDays(currentWeekStart, -7))}
>
<Icon name="chevronLeft" size={16} />
</button>
<span className="calendar-title">
{formatWeekRange(currentWeekStart)}
</span>
<button
className="calendar-nav"
onClick={() => setCurrentWeekStart(addDays(currentWeekStart, 7))}
>
<Icon name="chevronRight" size={16} />
</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" />}
</div>
)
})}
</div>
</section>
{/* Coach Section with Today's Action */}
<section className="coach-section">
<div className="coach-greeting">
<div className="coach-avatar">
<Icon name="coach" size={36} />
</div>
<div className="coach-message">
<p>{getCoachGreeting(user, todayWorkout)}</p>
</div>
</div>
{/* Today's Action */}
<div className="today-action">
{todayWorkout ? (
// Workout today - show workout card
<div className="today-workout-card" onClick={() => onStartWorkout(todayWorkout)}>
<div className="workout-info">
<h3>{todayWorkout.name}</h3>
<span className="workout-meta">
{todayWorkout.exercises?.filter(e => e.name).length} övningar ~45 min
</span>
</div>
<div className="workout-action">
<Icon name="arrowRight" size={24} />
</div>
</div>
) : (
// Rest day - show tips + add button
<div className="rest-day-section">
<div className="rest-tips">
{restDayTips.map((tip, i) => (
<span key={i} className="tip-badge">
<Icon name={tip.iconName} size={16} />
{tip.text}
</span>
))}
</div>
<button
className="add-workout-btn"
onClick={() => onNavigate('select-workout')}
>
<Icon name="plus" size={20} />
<span>Lägg till pass</span>
</button>
</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 stat-icon"><Icon name="fire" size={28} /></span>
<span className="stat-label">Streak: 5</span>
</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