and AI recommnedation fix
This commit is contained in:
echo 2025-12-12 01:32:52 +01:00
parent 670e6675cc
commit 8353a24f6b
17 changed files with 134 additions and 138 deletions

Binary file not shown.

View File

@ -172,9 +172,9 @@ export async function POST(req: Request) {
const recommendation = await db.createRecommendation({ const recommendation = await db.createRecommendation({
id: crypto.randomUUID(), id: crypto.randomUUID(),
userId, userId,
fitnessProfileId: profile.userId, fitnessProfileId: profile.id,
type: 'ai_plan', type: 'ai_plan',
content: parsedResponse.recommendationText, recommendationText: parsedResponse.recommendationText,
activityPlan: parsedResponse.activityPlan, activityPlan: parsedResponse.activityPlan,
dietPlan: parsedResponse.dietPlan, dietPlan: parsedResponse.dietPlan,
status: 'pending' status: 'pending'

View File

@ -76,7 +76,7 @@ export async function POST(request: Request) {
userId, userId,
fitnessProfileId, fitnessProfileId,
type: 'ai_plan', type: 'ai_plan',
content: recommendationText, recommendationText: recommendationText,
activityPlan, activityPlan,
dietPlan, dietPlan,
status: status || 'pending' status: status || 'pending'
@ -90,7 +90,7 @@ export async function POST(request: Request) {
id: crypto.randomUUID(), id: crypto.randomUUID(),
userId, userId,
type, type,
content, recommendationText: content,
status: status || 'pending' status: status || 'pending'
}) })
return NextResponse.json(recommendation) return NextResponse.json(recommendation)
@ -122,8 +122,8 @@ export async function PUT(request: Request) {
const updated = await db.updateRecommendation(id, { const updated = await db.updateRecommendation(id, {
...(status && { status }), ...(status && { status }),
...(recommendationText && { content: recommendationText }), // Map legacy field ...(recommendationText && { recommendationText }),
...(content && { content }), ...(content && { recommendationText: content }),
...(activityPlan && { activityPlan }), ...(activityPlan && { activityPlan }),
...(dietPlan && { dietPlan }) ...(dietPlan && { dietPlan })
}) })

View File

@ -219,7 +219,7 @@ export default function RecommendationsPage() {
</div> </div>
<div className="space-y-2 text-sm mb-4"> <div className="space-y-2 text-sm mb-4">
<div> <div>
<span className="font-semibold">Advice:</span> {rec.content} <span className="font-semibold">Advice:</span> {rec.recommendationText}
</div> </div>
<div> <div>
<span className="font-semibold">Activity:</span> {rec.activityPlan} <span className="font-semibold">Activity:</span> {rec.activityPlan}

View File

@ -8,7 +8,7 @@ interface Recommendation {
id: string; id: string;
userId: string; userId: string;
type: "short_term" | "medium_term" | "long_term" | "ai_plan"; type: "short_term" | "medium_term" | "long_term" | "ai_plan";
content: string; recommendationText: string;
activityPlan?: string; activityPlan?: string;
dietPlan?: string; dietPlan?: string;
status: "pending" | "completed" | "approved" | "rejected"; status: "pending" | "completed" | "approved" | "rejected";
@ -97,7 +97,7 @@ export function Recommendations({ userId }: RecommendationsProps) {
}`} }`}
> >
<div className="w-full"> <div className="w-full">
<p className="text-sm font-medium">{rec.content}</p> <p className="text-gray-700">{rec.recommendationText}</p>
{rec.type === 'ai_plan' && ( {rec.type === 'ai_plan' && (
<div className="mt-2 text-xs text-gray-600 space-y-1"> <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.activityPlan && <p><span className="font-semibold">Activity:</span> {rec.activityPlan}</p>}

View File

@ -224,14 +224,14 @@ export function UserGrid({
const gridRef = React.useRef<AgGridReact<User>>(null); const gridRef = React.useRef<AgGridReact<User>>(null);
const gridOptions = { const gridOptions = {
theme: "legacy", theme: "legacy" as const,
columnDefs, columnDefs,
defaultColDef, defaultColDef,
rowData: users, rowData: users,
rowSelection: "multiple", rowSelection: "multiple" as const,
onSelectionChanged: () => { onSelectionChanged: () => {
const selectedNodes = gridRef.current?.api.getSelectedNodes(); const selectedNodes = gridRef.current?.api.getSelectedNodes();
const selectedData = selectedNodes?.map((node) => node.data) || []; const selectedData = selectedNodes?.map((node) => node.data).filter((u): u is User => !!u) || [];
setSelectedUsers(selectedData); setSelectedUsers(selectedData);
if (selectedData.length === 1 && onUserSelect) { if (selectedData.length === 1 && onUserSelect) {
onUserSelect(selectedData[0]); onUserSelect(selectedData[0]);

View File

@ -1,17 +1,11 @@
import { getDatabase } from '@/lib/database'; import { getDatabase } from '@/lib/database';
import type { FitnessGoal } from '@/lib/database/types'; import type { FitnessGoal, FitnessProfile, Recommendation } from '@/lib/database/types';
// Import types from database package
import type {
FitnessProfile as DBFitnessProfile,
Recommendation as DBRecommendation,
} from '@fitai/database';
export interface AIContext { export interface AIContext {
profile: DBFitnessProfile; profile: FitnessProfile;
activeGoals: FitnessGoal[]; activeGoals: FitnessGoal[];
completedGoals: FitnessGoal[]; completedGoals: FitnessGoal[];
recentRecommendations: DBRecommendation[]; recentRecommendations: Recommendation[];
progressSummary: { progressSummary: {
goalsCompleted: number; goalsCompleted: number;
goalsActive: number; goalsActive: number;

View File

@ -32,7 +32,7 @@ export function buildEnhancedPrompt(context: AIContext): string {
- Weight: ${profile.weight || 'Not specified'} kg - Weight: ${profile.weight || 'Not specified'} kg
- Age: ${profile.age || 'Not specified'} - Age: ${profile.age || 'Not specified'}
- Gender: ${profile.gender || 'Not specified'} - Gender: ${profile.gender || 'Not specified'}
- Primary Goal: ${profile.fitnessGoal || 'General fitness'} - Primary Goal: ${profile.fitnessGoals?.[0] || 'General fitness'}
- Activity Level: ${profile.activityLevel || 'Not specified'} - Activity Level: ${profile.activityLevel || 'Not specified'}
- Medical Conditions: ${profile.medicalConditions || 'None'} - Medical Conditions: ${profile.medicalConditions || 'None'}
- Allergies: ${profile.allergies || 'None'} - Allergies: ${profile.allergies || 'None'}
@ -77,7 +77,7 @@ Generate a detailed daily recommendation for a user with the following profile:
- Weight: ${profile.weight} kg - Weight: ${profile.weight} kg
- Age: ${profile.age} - Age: ${profile.age}
- Gender: ${profile.gender} - Gender: ${profile.gender}
- Goal: ${profile.fitnessGoal} - Goal: ${profile.fitnessGoals?.[0]}
- Activity Level: ${profile.activityLevel} - Activity Level: ${profile.activityLevel}
- Medical Conditions: ${profile.medicalConditions || "None"} - Medical Conditions: ${profile.medicalConditions || "None"}
- Injuries: ${profile.injuries || "None"} - Injuries: ${profile.injuries || "None"}

View File

@ -246,7 +246,7 @@ export class DrizzleDatabase implements IDatabase {
} }
// Fitness Profile operations // Fitness Profile operations
async createFitnessProfile(profileData: Omit<FitnessProfile, 'createdAt' | 'updatedAt'>): Promise<FitnessProfile> { async createFitnessProfile(profileData: Omit<FitnessProfile, 'id' | 'createdAt' | 'updatedAt'>): Promise<FitnessProfile> {
const now = new Date() const now = new Date()
const id = Math.random().toString(36).substr(2, 9) const id = Math.random().toString(36).substr(2, 9)
const newProfile = { const newProfile = {
@ -257,7 +257,7 @@ export class DrizzleDatabase implements IDatabase {
} }
await this.db.insert(fitnessProfiles).values(newProfile as any) await this.db.insert(fitnessProfiles).values(newProfile as any)
return { ...profileData, createdAt: now, updatedAt: now } return { id, ...profileData, createdAt: now, updatedAt: now }
} }
async getFitnessProfileByUserId(userId: string): Promise<FitnessProfile | null> { async getFitnessProfileByUserId(userId: string): Promise<FitnessProfile | null> {

View File

@ -1,86 +1,12 @@
import { User as SharedUser, Client, FitnessProfile, Attendance, Recommendation, FitnessGoal } from "@fitai/shared";
// Database Entity Types // Database Entity Types
export interface User { export interface User extends SharedUser {
id: string;
email: string;
firstName: string;
lastName: string;
password: string; password: string;
phone?: string;
role: "superAdmin" | "admin" | "trainer" | "client";
createdAt: Date;
updatedAt: Date;
} }
export interface Client { export type { Client, FitnessProfile, Attendance, Recommendation, FitnessGoal };
id: string;
userId: string;
membershipType: "basic" | "premium" | "vip";
membershipStatus: "active" | "inactive" | "expired";
joinDate: Date;
lastVisit?: Date;
}
export interface FitnessProfile {
userId: string;
height: string;
weight: string;
age: string;
gender: "male" | "female" | "other";
activityLevel: "sedentary" | "lightly_active" | "moderately_active" | "very_active" | "extremely_active";
fitnessGoals: string[];
exerciseHabits: string;
dietHabits: string;
medicalConditions: string;
allergies?: string;
injuries?: string;
createdAt: Date;
updatedAt: Date;
}
export interface Attendance {
id: string;
userId: string;
checkInTime: Date;
checkOutTime?: Date;
type: "gym" | "class" | "personal_training";
notes?: string;
createdAt: Date;
}
export interface Recommendation {
id: string;
userId: string;
fitnessProfileId?: string;
type: "short_term" | "medium_term" | "long_term" | "ai_plan";
content: string;
activityPlan?: string;
dietPlan?: string;
status: "pending" | "approved" | "rejected" | "completed";
createdAt: Date;
approvedAt?: Date;
approvedBy?: string;
}
export interface FitnessGoal {
id: string;
userId: string;
fitnessProfileId?: string;
goalType: "weight_target" | "strength_milestone" | "endurance_target" | "flexibility_goal" | "habit_building" | "custom";
title: string;
description?: string;
targetValue?: number;
currentValue?: number;
unit?: string;
startDate: Date;
targetDate?: Date;
completedDate?: Date;
status: "active" | "completed" | "abandoned" | "paused";
progress: number;
priority: "low" | "medium" | "high";
notes?: string;
createdAt: Date;
updatedAt: Date;
}
// Database Interface - allows us to swap implementations // Database Interface - allows us to swap implementations
export interface IDatabase { export interface IDatabase {

View File

@ -1,14 +1 @@
import { type ClassValue, clsx } from "clsx" export { cn, formatDate } from "@fitai/shared"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function formatDate(date: string | Date) {
return new Date(date).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
}

View File

@ -9,7 +9,7 @@ import { theme } from "../../styles/theme";
interface Recommendation { interface Recommendation {
id: string; id: string;
userId: string; userId: string;
content: string; recommendationText: string;
activityPlan?: string; activityPlan?: string;
dietPlan?: string; dietPlan?: string;
status: string; status: string;
@ -141,7 +141,7 @@ export default function RecommendationsScreen() {
</View> </View>
<Text style={styles.sectionTitle}>Daily Advice</Text> <Text style={styles.sectionTitle}>Daily Advice</Text>
<Text style={styles.content}>{item.content}</Text> <Text style={styles.content}>{item.recommendationText}</Text>
{item.activityPlan && ( {item.activityPlan && (
<> <>

View File

@ -15,7 +15,7 @@
- Shared types should be utilized from `@fitai/shared` or `@fitai/database`. - Shared types should be utilized from `@fitai/shared` or `@fitai/database`.
- **Configuration**: - **Configuration**:
- `apps/mobile/src/config/api.ts` contains hardcoded URLs (e.g., ngrok) which is brittle for development and production. - `apps/mobile/src/config/api.ts` contains hardcoded URLs (e.g., ngrok) which is brittle for development and production.
- API routes in `apps/admin` contain `console.log` statements that should be removed or replaced with a proper logging solution. - API routes in `apps/admin` contain `console.log` statements that should be removed or replaced with a proper logging solution.https://5e424f097c8b.ngrok-free.app
- **Error Handling**: - **Error Handling**:
- Basic error handling exists in API routes, but could be standardized (e.g., custom error classes, consistent error response format). - Basic error handling exists in API routes, but could be standardized (e.g., custom error classes, consistent error response format).

View File

@ -8,12 +8,33 @@
"name": "@fitai/shared", "name": "@fitai/shared",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"clsx": "^2.1.1",
"tailwind-merge": "^3.4.0",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/tailwind-merge": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.9.3", "version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",

View File

@ -9,6 +9,8 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"clsx": "^2.1.1",
"tailwind-merge": "^3.4.0",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {

View File

@ -3,7 +3,10 @@ export interface User {
email: string email: string
firstName: string firstName: string
lastName: string lastName: string
role: 'admin' | 'trainer' | 'client' password?: string
phone?: string
role: 'superAdmin' | 'admin' | 'trainer' | 'client'
imageUrl?: string
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
} }
@ -11,18 +14,83 @@ export interface User {
export interface Client { export interface Client {
id: string id: string
userId: string userId: string
user: User user?: User
membershipType: 'basic' | 'premium' | 'vip' membershipType: 'basic' | 'premium' | 'vip'
membershipStatus: 'active' | 'inactive' | 'suspended' membershipStatus: 'active' | 'inactive' | 'suspended' | 'expired'
joinDate: Date joinDate: Date
lastVisit?: Date lastVisit?: Date
emergencyContact: { emergencyContact?: {
name: string name: string
phone: string phone: string
relationship: string relationship: string
} }
} }
export interface FitnessProfile {
id: string
userId: string
height: string
weight: string
age: string
gender: "male" | "female" | "other"
activityLevel: "sedentary" | "lightly_active" | "moderately_active" | "very_active" | "extremely_active"
fitnessGoals: string[]
exerciseHabits: string
dietHabits: string
medicalConditions: string
allergies?: string
injuries?: string
createdAt: Date
updatedAt: Date
}
export interface Attendance {
id: string
userId: string
clientId?: string
client?: Client
checkInTime: Date
checkOutTime?: Date
type: 'gym' | 'class' | 'personal_training'
notes?: string
createdAt?: Date
}
export interface Recommendation {
id: string
userId: string
fitnessProfileId?: string
type: "short_term" | "medium_term" | "long_term" | "ai_plan"
recommendationText: string
activityPlan?: string
dietPlan?: string
status: "pending" | "approved" | "rejected" | "completed"
createdAt: Date
approvedAt?: Date
approvedBy?: string
}
export interface FitnessGoal {
id: string
userId: string
fitnessProfileId?: string
goalType: "weight_target" | "strength_milestone" | "endurance_target" | "flexibility_goal" | "habit_building" | "custom"
title: string
description?: string
targetValue?: number
currentValue?: number
unit?: string
startDate: Date
targetDate?: Date
completedDate?: Date
status: "active" | "completed" | "abandoned" | "paused"
progress: number
priority: "low" | "medium" | "high"
notes?: string
createdAt: Date
updatedAt: Date
}
export interface Payment { export interface Payment {
id: string id: string
clientId: string clientId: string
@ -36,16 +104,6 @@ export interface Payment {
description: string description: string
} }
export interface Attendance {
id: string
clientId: string
client: Client
checkInTime: Date
checkOutTime?: Date
type: 'gym' | 'class' | 'personal_training'
notes?: string
}
export interface Notification { export interface Notification {
id: string id: string
userId: string userId: string

View File

@ -1,9 +1,17 @@
export const formatDate = (date: Date): string => { import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export const formatDate = (date: Date | string): string => {
const d = new Date(date)
return new Intl.DateTimeFormat('en-US', { return new Intl.DateTimeFormat('en-US', {
year: 'numeric', year: 'numeric',
month: 'short', month: 'long',
day: 'numeric', day: 'numeric',
}).format(date) }).format(d)
} }
export const formatCurrency = ( export const formatCurrency = (