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:
@@ -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
|
||||
Reference in New Issue
Block a user