206 lines
6.3 KiB
TypeScript
206 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
|
import log from "@/lib/logger";
|
|
|
|
interface Recommendation {
|
|
id: string;
|
|
userId: string;
|
|
type: "short_term" | "medium_term" | "long_term" | "ai_plan";
|
|
recommendationText: string;
|
|
activityPlan?: string;
|
|
dietPlan?: string;
|
|
status: "pending" | "completed" | "approved" | "rejected";
|
|
createdAt: string;
|
|
}
|
|
|
|
interface RecommendationsProps {
|
|
userId: string;
|
|
}
|
|
|
|
export function Recommendations({ userId }: RecommendationsProps) {
|
|
const [recommendations, setRecommendations] = useState<Recommendation[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [newRec, setNewRec] = useState<{
|
|
type: "short_term" | "medium_term" | "long_term";
|
|
content: string;
|
|
}>({ type: "short_term", content: "" });
|
|
|
|
useEffect(() => {
|
|
fetchRecommendations();
|
|
}, [userId]);
|
|
|
|
const fetchRecommendations = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await fetch(`/api/recommendations?userId=${userId}`);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setRecommendations(data);
|
|
}
|
|
} catch (error) {
|
|
log.error("Failed to fetch recommendations", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleAddRecommendation = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
try {
|
|
const response = await fetch("/api/recommendations", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
userId,
|
|
type: newRec.type,
|
|
content: newRec.content,
|
|
}),
|
|
});
|
|
|
|
if (response.ok) {
|
|
setNewRec({ ...newRec, content: "" });
|
|
fetchRecommendations();
|
|
} else {
|
|
alert("Failed to add recommendation");
|
|
}
|
|
} catch (error) {
|
|
log.error("Failed to add recommendation", error);
|
|
}
|
|
};
|
|
|
|
const groupedRecs = {
|
|
ai_plan: recommendations.filter((r) => r.type === "ai_plan"),
|
|
short_term: recommendations.filter((r) => r.type === "short_term"),
|
|
medium_term: recommendations.filter((r) => r.type === "medium_term"),
|
|
long_term: recommendations.filter((r) => r.type === "long_term"),
|
|
};
|
|
|
|
const renderSection = (
|
|
title: string,
|
|
type: "short_term" | "medium_term" | "long_term" | "ai_plan",
|
|
items: Recommendation[],
|
|
) => (
|
|
<div className="mb-6">
|
|
<h4 className="font-semibold text-lg mb-3 capitalize">{title}</h4>
|
|
<div className="space-y-2">
|
|
{items.length === 0 && (
|
|
<p className="text-gray-500 text-sm italic">
|
|
No recommendations yet.
|
|
</p>
|
|
)}
|
|
{items.map((rec) => (
|
|
<div
|
|
key={rec.id}
|
|
className={`p-3 rounded border flex justify-between items-start ${
|
|
rec.status === "completed" || rec.status === "approved"
|
|
? "bg-green-50 border-green-200"
|
|
: "bg-white border-gray-200"
|
|
}`}
|
|
>
|
|
<div className="w-full">
|
|
<p className="text-gray-700">{rec.recommendationText}</p>
|
|
{rec.type === "ai_plan" && (
|
|
<div className="mt-2 text-xs text-gray-600 space-y-1">
|
|
{rec.activityPlan && (
|
|
<p>
|
|
<span className="font-semibold">Activity:</span>{" "}
|
|
{rec.activityPlan}
|
|
</p>
|
|
)}
|
|
{rec.dietPlan && (
|
|
<p>
|
|
<span className="font-semibold">Diet:</span>{" "}
|
|
{rec.dietPlan}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
<p className="text-xs text-gray-400 mt-2">
|
|
{new Date(rec.createdAt).toLocaleDateString()} -{" "}
|
|
<span
|
|
className={
|
|
rec.status === "completed" || rec.status === "approved"
|
|
? "text-green-600 font-medium"
|
|
: "text-yellow-600"
|
|
}
|
|
>
|
|
{rec.status === "completed"
|
|
? "Completed"
|
|
: rec.status === "approved"
|
|
? "Approved"
|
|
: "Pending"}
|
|
</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
{type !== "ai_plan" && (
|
|
<form onSubmit={handleAddRecommendation} className="mt-3 flex gap-2">
|
|
<input
|
|
type="hidden"
|
|
value={type}
|
|
onChange={() => setNewRec({ ...newRec, type: type as any })}
|
|
/>
|
|
{newRec.type === type && (
|
|
<>
|
|
<input
|
|
type="text"
|
|
placeholder={`Add ${title.toLowerCase()}...`}
|
|
className="flex-1 border rounded px-3 py-1 text-sm"
|
|
value={newRec.content}
|
|
onChange={(e) =>
|
|
setNewRec({ ...newRec, content: e.target.value })
|
|
}
|
|
required
|
|
/>
|
|
<Button type="submit" variant="secondary">
|
|
Add
|
|
</Button>
|
|
</>
|
|
)}
|
|
{newRec.type !== type && (
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
onClick={() => setNewRec({ type: type as any, content: "" })}
|
|
className="text-xs text-gray-500"
|
|
>
|
|
+ Add New
|
|
</Button>
|
|
)}
|
|
</form>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
if (loading) return <div>Loading recommendations...</div>;
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<h3 className="text-xl font-bold">Fitness Recommendations</h3>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{renderSection("Daily AI Plan", "ai_plan", groupedRecs.ai_plan)}
|
|
{renderSection(
|
|
"Short Term Goals",
|
|
"short_term",
|
|
groupedRecs.short_term,
|
|
)}
|
|
{renderSection(
|
|
"Medium Term Goals",
|
|
"medium_term",
|
|
groupedRecs.medium_term,
|
|
)}
|
|
{renderSection("Long Term Goals", "long_term", groupedRecs.long_term)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|