From d86abd6b1bd41027c1dcdc7db0f8a78dce309426 Mon Sep 17 00:00:00 2001 From: AG Date: Sat, 29 Nov 2025 12:13:12 +0200 Subject: [PATCH] Separated weight tracking --- App.tsx | 30 ++++++----- components/Profile.tsx | 91 +++++++++++++++++++++++++++++++++- components/Stats.tsx | 26 ++++++---- server/prisma/dev.db | Bin 61440 -> 73728 bytes server/prisma/schema.prisma | 12 +++++ server/src/index.ts | 2 + server/src/middleware/auth.ts | 21 ++++++++ server/src/routes/weight.ts | 78 +++++++++++++++++++++++++++++ services/i18n.ts | 2 + services/weight.ts | 53 ++++++++++++++++++++ types.ts | 7 +++ 11 files changed, 300 insertions(+), 22 deletions(-) create mode 100644 server/src/middleware/auth.ts create mode 100644 server/src/routes/weight.ts create mode 100644 services/weight.ts diff --git a/App.tsx b/App.tsx index 3afa478..2651a0b 100644 --- a/App.tsx +++ b/App.tsx @@ -12,6 +12,7 @@ import { TabView, WorkoutSession, WorkoutSet, WorkoutPlan, User, Language } from import { getSessions, saveSession, deleteSession, getPlans, getActiveSession, updateActiveSession, deleteActiveSession, updateSetInActiveSession, deleteSetFromActiveSession } from './services/storage'; import { getCurrentUserProfile, getMe } from './services/auth'; import { getSystemLanguage } from './services/i18n'; +import { logWeight } from './services/weight'; import { generateId } from './utils/uuid'; function App() { @@ -114,6 +115,11 @@ function App() { // Save to database immediately await saveSession(currentUser.id, newSession); + + // If startWeight was provided (meaning user explicitly entered it), log it to weight history + if (startWeight) { + await logWeight(startWeight); + } }; const handleEndSession = async () => { @@ -144,23 +150,23 @@ function App() { const handleRemoveSetFromActive = async (setId: string) => { if (activeSession && currentUser) { - await deleteSetFromActiveSession(currentUser.id, setId); - const updatedSession = { - ...activeSession, - sets: activeSession.sets.filter(s => s.id !== setId) - }; - setActiveSession(updatedSession); + await deleteSetFromActiveSession(currentUser.id, setId); + const updatedSession = { + ...activeSession, + sets: activeSession.sets.filter(s => s.id !== setId) + }; + setActiveSession(updatedSession); } }; const handleUpdateSetInActive = async (updatedSet: WorkoutSet) => { if (activeSession && currentUser) { - const response = await updateSetInActiveSession(currentUser.id, updatedSet.id, updatedSet); - const updatedSession = { - ...activeSession, - sets: activeSession.sets.map(s => s.id === updatedSet.id ? response : s) - }; - setActiveSession(updatedSession); + const response = await updateSetInActiveSession(currentUser.id, updatedSet.id, updatedSet); + const updatedSession = { + ...activeSession, + sets: activeSession.sets.map(s => s.id === updatedSet.id ? response : s) + }; + setActiveSession(updatedSession); } }; diff --git a/components/Profile.tsx b/components/Profile.tsx index 2dbd7c2..b1059bf 100644 --- a/components/Profile.tsx +++ b/components/Profile.tsx @@ -1,8 +1,9 @@ import React, { useState, useEffect } from 'react'; -import { User, Language, ExerciseDef, ExerciseType } from '../types'; +import { User, Language, ExerciseDef, ExerciseType, BodyWeightRecord } from '../types'; import { createUser, changePassword, updateUserProfile, getCurrentUserProfile, getUsers, deleteUser, toggleBlockUser, adminResetPassword, getMe } from '../services/auth'; import { getExercises, saveExercise } from '../services/storage'; +import { getWeightHistory, logWeight } from '../services/weight'; import { User as UserIcon, LogOut, Save, Shield, UserPlus, Lock, Calendar, Ruler, Scale, PersonStanding, Globe, ChevronDown, ChevronUp, Trash2, Ban, KeyRound, Dumbbell, Archive, ArchiveRestore, Pencil, Plus } from 'lucide-react'; import ExerciseModal from './ExerciseModal'; import { t } from '../services/i18n'; @@ -23,6 +24,11 @@ const Profile: React.FC = ({ user, onLogout, lang, onLanguageChang const [birthDate, setBirthDate] = useState(''); const [gender, setGender] = useState('MALE'); + // Weight Tracker + const [weightHistory, setWeightHistory] = useState([]); + const [todayWeight, setTodayWeight] = useState(''); + const [showWeightTracker, setShowWeightTracker] = useState(false); + // Admin: Create User const [newUserEmail, setNewUserEmail] = useState(''); const [newUserPass, setNewUserPass] = useState(''); @@ -72,9 +78,22 @@ const Profile: React.FC = ({ user, onLogout, lang, onLanguageChang refreshUserList(); } refreshExercises(); + refreshWeightHistory(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [user.id, user.role, JSON.stringify(user.profile)]); + const refreshWeightHistory = async () => { + const history = await getWeightHistory(); + setWeightHistory(history); + + // Check if we have a weight for today + const today = new Date().toISOString().split('T')[0]; + const todayRecord = history.find(r => r.dateStr === today); + if (todayRecord) { + setTodayWeight(todayRecord.weight.toString()); + } + }; + const refreshUserList = async () => { const res = await getUsers(); if (res.success && res.users) { @@ -99,6 +118,27 @@ const Profile: React.FC = ({ user, onLogout, lang, onLanguageChang setSnackbar({ isOpen: true, message, type }); }; + const handleLogWeight = async () => { + if (!todayWeight) return; + const weightVal = parseFloat(todayWeight); + if (isNaN(weightVal)) return; + + const res = await logWeight(weightVal); + if (res) { + showSnackbar('Weight logged successfully', 'success'); + refreshWeightHistory(); + // Also update the profile weight display if it's today + setWeight(todayWeight); + // And trigger user update to sync across app + const userRes = await getMe(); + if (userRes.success && userRes.user && onUserUpdate) { + onUserUpdate(userRes.user); + } + } else { + showSnackbar('Failed to log weight', 'error'); + } + }; + const handleSaveProfile = async () => { const res = await updateUserProfile(user.id, { weight: parseFloat(weight) || undefined, @@ -267,6 +307,55 @@ const Profile: React.FC = ({ user, onLogout, lang, onLanguageChang + {/* WEIGHT TRACKER */} +
+ + + {showWeightTracker && ( +
+
+
+ + setTodayWeight(e.target.value)} + className="w-full bg-transparent text-on-surface focus:outline-none" + placeholder="Enter weight..." + /> +
+ +
+ +
+

History

+ {weightHistory.length === 0 ? ( +

No weight records yet.

+ ) : ( + weightHistory.map(record => ( +
+ {new Date(record.date).toLocaleDateString()} + {record.weight} kg +
+ )) + )} +
+
+ )} +
+ {/* EXERCISE MANAGER */}