fitness: add complete fitness tracker frontend
- 5-tab layout (Profile, History, Workout, Exercises, Measure) with shared header nav - Workout system: template CRUD, active workout on /fitness/workout/active with localStorage persistence, pause/resume timer, rest timer, RPE input - Shared workout singleton (getWorkout) so active workout state is accessible across all fitness routes - Floating workout FAB indicator on all /fitness routes when workout is active - AddActionButton component for button-based FABs (measure + template creation) - Profile page with workouts-per-week bar chart and weight line chart with SMA trend line + ±1σ confidence band - Exercise detail with history, charts, and records tabs using static exercise data - Session history with grouped-by-month list, session detail with stats/PRs - Body measurements with latest values, body part display, add form - Card styling matching rosary/prayer route patterns (accent-dark, nord5 light, box-shadow, hover lift) - FitnessChart: fix SSR hang by moving Chart.register to client-side, remove redundant $effect - Exercise API: use static in-repo data instead of empty MongoDB collection - Workout finish: include exercise name for WorkoutSession model validation
This commit is contained in:
99
src/models/BodyMeasurement.ts
Normal file
99
src/models/BodyMeasurement.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export interface IBodyPartMeasurements {
|
||||
neck?: number;
|
||||
shoulders?: number;
|
||||
chest?: number;
|
||||
leftBicep?: number;
|
||||
rightBicep?: number;
|
||||
leftForearm?: number;
|
||||
rightForearm?: number;
|
||||
waist?: number;
|
||||
hips?: number;
|
||||
leftThigh?: number;
|
||||
rightThigh?: number;
|
||||
leftCalf?: number;
|
||||
rightCalf?: number;
|
||||
}
|
||||
|
||||
export interface IBodyMeasurement {
|
||||
_id?: string;
|
||||
date: Date;
|
||||
weight?: number;
|
||||
bodyFatPercent?: number;
|
||||
caloricIntake?: number;
|
||||
measurements?: IBodyPartMeasurements;
|
||||
notes?: string;
|
||||
createdBy: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
const BodyPartMeasurementsSchema = new mongoose.Schema(
|
||||
{
|
||||
neck: { type: Number, min: 0 },
|
||||
shoulders: { type: Number, min: 0 },
|
||||
chest: { type: Number, min: 0 },
|
||||
leftBicep: { type: Number, min: 0 },
|
||||
rightBicep: { type: Number, min: 0 },
|
||||
leftForearm: { type: Number, min: 0 },
|
||||
rightForearm: { type: Number, min: 0 },
|
||||
waist: { type: Number, min: 0 },
|
||||
hips: { type: Number, min: 0 },
|
||||
leftThigh: { type: Number, min: 0 },
|
||||
rightThigh: { type: Number, min: 0 },
|
||||
leftCalf: { type: Number, min: 0 },
|
||||
rightCalf: { type: Number, min: 0 }
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const BodyMeasurementSchema = new mongoose.Schema(
|
||||
{
|
||||
date: {
|
||||
type: Date,
|
||||
required: true,
|
||||
default: Date.now
|
||||
},
|
||||
weight: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
max: 500
|
||||
},
|
||||
bodyFatPercent: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
max: 100
|
||||
},
|
||||
caloricIntake: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
max: 50000
|
||||
},
|
||||
measurements: {
|
||||
type: BodyPartMeasurementsSchema
|
||||
},
|
||||
notes: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 500
|
||||
},
|
||||
createdBy: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
toJSON: { virtuals: true },
|
||||
toObject: { virtuals: true }
|
||||
}
|
||||
);
|
||||
|
||||
BodyMeasurementSchema.index({ createdBy: 1, date: -1 });
|
||||
|
||||
export const BodyMeasurement = mongoose.model<IBodyMeasurement>(
|
||||
'BodyMeasurement',
|
||||
BodyMeasurementSchema
|
||||
);
|
||||
Reference in New Issue
Block a user