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>
This commit is contained in:
2026-03-02 19:20:40 +01:00
parent 53f026aee2
commit 83ccd6c601
5 changed files with 408 additions and 0 deletions
@@ -0,0 +1,107 @@
import { useState } from 'react'
const API_URL = '/api'
function ExerciseResearchPanel({ exerciseId, exerciseName }) {
const [loading, setLoading] = useState(false)
const [research, setResearch] = useState(null)
const [error, setError] = useState(null)
const fetchResearch = async () => {
setLoading(true)
setError(null)
try {
const res = await fetch(`${API_URL}/exercises/${exerciseId}/research`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
})
if (!res.ok) {
const data = await res.json()
throw new Error(data.error || 'Failed to fetch research')
}
const data = await res.json()
setResearch(data)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
return (
<div className="research-panel">
<div className="research-panel-header">
<h3 className="research-panel-title">Research</h3>
{!research && (
<button
className="btn btn-primary research-btn"
onClick={fetchResearch}
disabled={loading}
>
{loading ? 'Fetching...' : 'Get Research'}
</button>
)}
{research && (
<button
className="btn btn-secondary research-btn"
onClick={fetchResearch}
disabled={loading}
>
{loading ? 'Fetching...' : 'Refresh'}
</button>
)}
</div>
{loading && (
<div className="research-loading">
<div className="research-spinner"></div>
<span>Searching for information on {exerciseName}...</span>
</div>
)}
{error && (
<div className="research-error">
<span>{error}</span>
<button className="btn-close" onClick={() => setError(null)}>×</button>
</div>
)}
{research && !loading && (
<div className="research-results">
{research.summary && (
<div className="research-summary">
<h4>Summary</h4>
<p>{research.summary}</p>
</div>
)}
{research.results && research.results.length > 0 && (
<div className="research-sources">
<h4>Sources</h4>
<ul className="research-sources-list">
{research.results.map((result, i) => (
<li key={i} className="research-source-item">
<a
href={result.url}
target="_blank"
rel="noopener noreferrer"
className="research-source-link"
>
{result.title}
</a>
{result.snippet && (
<p className="research-source-snippet">{result.snippet}</p>
)}
</li>
))}
</ul>
</div>
)}
</div>
)}
</div>
)
}
export default ExerciseResearchPanel