cleanup
and AI recommnedation fix
This commit is contained in:
parent
670e6675cc
commit
8353a24f6b
Binary file not shown.
@ -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'
|
||||||
|
|||||||
@ -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 })
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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>}
|
||||||
|
|||||||
@ -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]);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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"}
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -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 && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -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).
|
||||||
|
|
||||||
|
|||||||
21
packages/shared/package-lock.json
generated
21
packages/shared/package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -9,9 +9,11 @@
|
|||||||
"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": {
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 = (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user