Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions olympicmind/frontend/src/components/AthleteCheckInForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { useState } from 'react';
import axios from 'axios';

const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';

const metricFields = [
{ key: 'sleepQuality', label: 'Sleep Quality' },
{ key: 'stressLevel', label: 'Stress Level' },
{ key: 'physicalFatigue', label: 'Physical Fatigue' }
];

const toggleFields = [
{ key: 'followedNutritionPlan', label: 'Did you follow your nutrition plan?' },
{ key: 'hasJetlagSymptoms', label: 'Are you experiencing jetlag symptoms?' }
];

const AthleteCheckInForm = () => {
const [formData, setFormData] = useState({
sleepQuality: 7,
stressLevel: 4,
physicalFatigue: 5,
followedNutritionPlan: true,
hasJetlagSymptoms: false
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [toast, setToast] = useState(null);

const showToast = (type, message) => {
setToast({ type, message });
setTimeout(() => setToast(null), 3000);
};

const handleMetricChange = (key, value) => {
setFormData((prev) => ({ ...prev, [key]: Number(value) }));
};

const handleToggleChange = (key, value) => {
setFormData((prev) => ({ ...prev, [key]: value }));
};

const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
try {
await axios.post(`${API_BASE_URL}/api/v1/audit`, {
sleep_quality: formData.sleepQuality,
stress_level: formData.stressLevel,
physical_fatigue: formData.physicalFatigue,
followed_nutrition_plan: formData.followedNutritionPlan,
has_jetlag_symptoms: formData.hasJetlagSymptoms
});
showToast('success', 'Check-in submitted successfully.');
} catch (err) {
console.error('Failed to submit check-in:', err?.message || 'Unknown error');
showToast('error', 'Unable to submit check-in. Please try again.');
} finally {
setIsSubmitting(false);
}
};

return (
<form onSubmit={handleSubmit} className="space-y-5">
{metricFields.map((field) => (
<div key={field.key} className="space-y-2">
<div className="flex justify-between items-center text-sm">
<label htmlFor={field.key} className="font-medium text-gray-100">{field.label}</label>
<span className="font-semibold text-accent">{formData[field.key]} / 10</span>
</div>
<input
id={field.key}
type="range"
min="1"
max="10"
value={formData[field.key]}
onChange={(e) => handleMetricChange(field.key, e.target.value)}
className="w-full accent-primary"
/>
</div>
))}

{toggleFields.map((field) => (
<div key={field.key} className="space-y-2">
<p className="text-sm font-medium text-gray-100">{field.label}</p>
<div className="grid grid-cols-2 gap-2">
<button
type="button"
onClick={() => handleToggleChange(field.key, true)}
className={`px-3 py-2 rounded-lg border text-sm font-medium transition ${
formData[field.key]
? 'bg-accent/20 border-accent/40 text-accent'
: 'bg-surfaceHover/50 border-white/10 text-gray-300 hover:bg-surfaceHover/70'
}`}
>
Yes
</button>
<button
type="button"
onClick={() => handleToggleChange(field.key, false)}
className={`px-3 py-2 rounded-lg border text-sm font-medium transition ${
!formData[field.key]
? 'bg-accent/20 border-accent/40 text-accent'
: 'bg-surfaceHover/50 border-white/10 text-gray-300 hover:bg-surfaceHover/70'
}`}
>
No
</button>
</div>
</div>
))}

<button
type="submit"
disabled={isSubmitting}
className="w-full py-2.5 bg-gradient-to-r from-primary to-primaryHover hover:from-primaryHover hover:to-primary text-white text-sm font-semibold rounded-lg shadow-lg shadow-primary/20 transition-all disabled:opacity-50"
>
{isSubmitting ? 'Submitting...' : 'Submit Daily Check-in'}
</button>

{toast && (
<div
role="alert"
aria-live="polite"
className={`fixed bottom-4 right-4 z-50 px-4 py-3 rounded-lg border text-sm shadow-2xl ${
toast.type === 'success'
? 'bg-accent/20 border-accent/40 text-accent'
: 'bg-alert/20 border-alert/40 text-alert'
}`}
>
{toast.message}
</div>
)}
</form>
);
};

export default AthleteCheckInForm;
12 changes: 12 additions & 0 deletions olympicmind/frontend/src/components/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import MapView from './MapView';
import ChatInterface from './ChatInterface';
import AlertPanel from './AlertPanel';
import RoutePlanner from './RoutePlanner';
import AthleteCheckInForm from './AthleteCheckInForm';

const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';

Expand Down Expand Up @@ -84,6 +85,17 @@ const Dashboard = () => {
</div>
</div>

<div className="glass rounded-2xl overflow-hidden shadow-2xl flex flex-col">
<div className="px-5 py-4 border-b border-white/5 bg-surface/40">
<h2 className="text-lg font-semibold flex items-center gap-2">
<span className="text-xl">📝</span> Athlete Daily Check-in
</h2>
</div>
<div className="p-4">
<AthleteCheckInForm />
</div>
</div>

</div>
</div>
);
Expand Down