From 34738697df1ca09a8b9806e1e22e83fe91288147 Mon Sep 17 00:00:00 2001 From: aodulov Date: Wed, 26 Nov 2025 09:32:29 +0200 Subject: [PATCH] 1. Exercise lists sorted. 2. Session finishing confirmation. 3. `Quit without saving` option added. --- components/Plans.tsx | 23 +++++---- components/Tracker.tsx | 111 +++++++++++++++++++++++++++++++++++++---- server/prisma/dev.db | Bin 61440 -> 61440 bytes services/i18n.ts | 16 +++++- 4 files changed, 128 insertions(+), 22 deletions(-) diff --git a/components/Plans.tsx b/components/Plans.tsx index 652b3a6..b06edc0 100644 --- a/components/Plans.tsx +++ b/components/Plans.tsx @@ -275,16 +275,19 @@ const Plans: React.FC = ({ userId, onStartPlan, lang }) => {
- {availableExercises.map(ex => ( - - ))} + {availableExercises + .slice() + .sort((a, b) => a.name.localeCompare(b.name)) + .map(ex => ( + + ))}
{isCreatingExercise && ( diff --git a/components/Tracker.tsx b/components/Tracker.tsx index f44c3cb..81de3a2 100644 --- a/components/Tracker.tsx +++ b/components/Tracker.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { Plus, Activity, ChevronDown, ChevronUp, Dumbbell, PlayCircle, CheckCircle, User, Scale, X, Flame, Timer as TimerIcon, ArrowUp, ArrowRight, Footprints, Ruler, CheckSquare, Trash2, Percent } from 'lucide-react'; +import { Plus, Activity, ChevronDown, ChevronUp, Dumbbell, PlayCircle, CheckCircle, User, Scale, X, Flame, Timer as TimerIcon, ArrowUp, ArrowRight, Footprints, Ruler, CheckSquare, Trash2, Percent, MoreVertical } from 'lucide-react'; import { WorkoutSession, WorkoutSet, ExerciseDef, ExerciseType, WorkoutPlan, Language } from '../types'; import { getExercises, getLastSetForExercise, saveExercise, getPlans } from '../services/storage'; import { getCurrentUserProfile } from '../services/auth'; @@ -49,6 +49,11 @@ const Tracker: React.FC = ({ userId, userWeight, activeSession, ac const [showPlanPrep, setShowPlanPrep] = useState(null); const [showPlanList, setShowPlanList] = useState(false); + // Confirmation State + const [showFinishConfirm, setShowFinishConfirm] = useState(false); + const [showQuitConfirm, setShowQuitConfirm] = useState(false); + const [showMenu, setShowMenu] = useState(false); + useEffect(() => { const loadData = async () => { const exList = await getExercises(userId); @@ -284,12 +289,40 @@ const Tracker: React.FC = ({ userId, userWeight, activeSession, ac {activeSession.userBodyWeight ? ` • ${activeSession.userBodyWeight}kg` : ''} - +
+ + + {showMenu && ( + <> +
setShowMenu(false)} + /> +
+ +
+ + )} +
{activePlan && ( @@ -348,9 +381,12 @@ const Tracker: React.FC = ({ userId, userWeight, activeSession, ac onChange={(e) => setSelectedExercise(exercises.find(ex => ex.id === e.target.value) || null)} > - {exercises.map(ex => ( - - ))} + {exercises + .slice() + .sort((a, b) => a.name.localeCompare(b.name)) + .map(ex => ( + + ))} @@ -497,6 +533,61 @@ const Tracker: React.FC = ({ userId, userWeight, activeSession, ac lang={lang} /> )} + + {/* Finish Confirmation Dialog */} + {showFinishConfirm && ( +
+
+

{t('finish_confirm_title', lang)}

+

{t('finish_confirm_msg', lang)}

+
+ + +
+
+
+ )} + + {/* Quit Without Saving Confirmation Dialog */} + {showQuitConfirm && ( +
+
+

{t('quit_confirm_title', lang)}

+

{t('quit_confirm_msg', lang)}

+
+ + +
+
+
+ )} ); }; diff --git a/server/prisma/dev.db b/server/prisma/dev.db index a981361e4eddba375a93531481026169238946ae..59d7a26e1e6a5ecc5d0f9cc16af50385d69616cb 100644 GIT binary patch delta 3035 zcmb_eU5Hgx6u!rCX72Afjzd;xb{jR7+S55_|BIlFSx!SH)4f)tGwpr$J~KnpXkI6k zkn6k{M4)zqM4}=JdWb}CFZD3>5cSm0OEB~U)>99?6chyot$n8kEy{z$fyFuOeb)Nc z&$re(ajEOXrLNPfy|ZW5?C{QBc=XIRuh#x?+sYL;6idJ6f)~r$U+|ysy~+!fE#(hO zzXn^|eJj2$p@~9oy}t1lFCQixYZhB%WgH_SVvYpH5i-nB5ymv4Shu|K^=tP`J!Y$W zW*d{Jey}lg|6LD1xO4cPUA1w&3^c844ULbKhNcpGu)1UR&~&3czwb?NMX`R{P5FFk zm&29K@YYfX*0)|8+pv1}ajot2bal@m+h{yc8{N74p4u+m)zS3D*vR%FJCjUac{)~v z)0=P1=NX|!$b=zkB7%q!7^x(|2;*2`&M6j#ImE#(HDelwK|D~qvo^e=_2T$svFK0a z{L=yPC)&kFANKcd>Z*HL6c*jZEU_xMi>*)ht#5xbv(8)9Q>o>GSAy;SCI8;a+m%{* zzPzdQ9OU3=DV*ys6zcWvZZB8A!>Tw+G{;CJ44g_VLo!JTQYpqZ#7tmqoKsEj+{6`x$Z`u5@*R%7>(>;= zx&Gx#Mmfh8bCE(W4TWML5{fC2D8!UWnh0ze`G*f33~O$47>+NyHVk83OSz0mO0|v{ zG!v@9?^Ga(lMq>!gxCm6W5KR~(8*;5tQUS>2eSD4_x0BH<_D{Kf>JIh1t$X%><<=# z^X~CaxBIG3`b|f$EOhBGF-w8xoyII>*y}Dfv(!qDyUC)fRsrvJ7qet;xx47%TfJI< zTW_7(yQWZjHy1n|`2O?$>dLE?&E@lDQhK+vvp8BDh3*p<+oR_uz1q=2v0ks|b6(CX z@CY-5u|YV5$|aU-q)7s4Oj4PKhG}ec7MEII9avv4uRWG`szEWRrV?nVpfp36HBg!o zR72NA3^*Dx0aO=hh9I=m%Nv?q<+Vq97ggs#HRFV1ZV6&Bm4H=Hq&24~F-#^p)LPa2CGBr&|onhtd362Of&|I7~W-xrLl&&j|Xlaut)8o zWU66@-9Jm!wfes{ykF)YdMMG#L>PxCQi(w%rWOf{VF+rKY5@aK#_C_SUqyM-z`_^H zR3nPBGY1n9AH0~eMzop6}Be^;J`lb8LoTRm>U;ia|jvZLDmV+Y;Y;wA_OQ;o&= zS_o09rW^M7?SIA?OV|-8kjbqCFvAoq#IX&LG|DEHt3(=hndVN6UCV3TH$QUyemBZK j?^~3`95kq4Ar~yP``-U3IJ2jB)5flPAxpiy`dRf46^1e! delta 2280 zcma)7U1$|Y6y7zzH@&%=6f1&d?M4M6Z@9IiQa5t zd|R+|<)fYK!s`>Q?WLPuKx(D^yGwJIc4lYZZ_e(G57fS&e9m97EEw{FOTkbn|NU4x zRa#q|FU~_pXNzdO0BKuVe9tnbVi{AUqmUxvghRrn3Nh=P!JKG;(=4d277Hf1>YrP% zqesTZ+n0i|F$}wuAr>(Rrb;1Ar9(K4tdu&!8k2hP<)Q1R{BxIIN`4#jyk)F@v!*E~ zk>H3i%@EPlASDPzTv*Ox%Q%Vn!(ep!69`5ZdLY>QtRBo_%|n+8q%6&YU5I4lFtU-g z(hA2S!?WtD9{kpd*22S16DnkC3D*W;$sE;#4$#Ms|ZORb0l3HxhM>2sPxOT2M!=TWsa6dre+UUCT;nR(DVG=^-4XD z3THH6iKI>eC2k~uO)8ly0Y&|lD;uibg)zA11^0t%!MCt}sINQ0omzMKy%Nkz zOSORkD6DNt{6@%bhf;rTgxEGXI8rCE`Z$aEJ&mx`3gMSyN*J&p5#zB%grz!1I0bN( z#m=ypQPBbikNqbBZA%Pf^QU+AHJ5t5;GLjUI$P@P9PAu?>UMn%cZ_t5fN-LY+Q_*H zKN~vVGIq4kktB)l`<_=&G-fFw7#XHXUI|7D;}B71LLEA-nPMi6A|WG=umREtCK5?& zC<2$@iV31{lxC~W54^UlScIZ~Pm)4Cw`CaH$Pq*-%nZ>|Bdzi-m9{z298=R^?$x<| zjwMNZJ+V=tme^{aj>b8xqak)XmLP@%Z};1!|9xsD{b4Tv2X z%c&w(r6yzzChluG(%ez&z4(*g*BtEig1x~h5XgP_Cjlwle6)b_KRWBC+B1K=+6c=9 zKzO7P%5tTwYlMQfToXB_TmKK#TomgX0Vx~5cBR$p^1RN2&|%t%-|q6kBZq^mX}9>)mRm diff --git a/services/i18n.ts b/services/i18n.ts index 321b166..f765e9e 100644 --- a/services/i18n.ts +++ b/services/i18n.ts @@ -50,6 +50,12 @@ const translations = { cancel: 'Cancel', start: 'Start', finish: 'Finish', + finish_confirm_title: 'Finish Workout?', + finish_confirm_msg: 'Are you sure you want to finish this workout? Your progress will be saved.', + quit_no_save: 'Quit without saving', + quit_confirm_title: 'Quit without saving?', + quit_confirm_msg: 'All progress from this session will be lost. This action cannot be undone.', + confirm: 'Confirm', plan_completed: 'Plan Completed!', step: 'Step', of: 'of', @@ -71,7 +77,7 @@ const translations = { add_weight: 'Add. Weight', // Types - type_strength: 'Strength', + type_strength: 'Free Weights & Machines', type_bodyweight: 'Bodyweight', type_cardio: 'Cardio', type_static: 'Static', @@ -191,6 +197,12 @@ const translations = { cancel: 'Отмена', start: 'Начать', finish: 'Завершить', + finish_confirm_title: 'Завершить тренировку?', + finish_confirm_msg: 'Вы уверены, что хотите завершить эту тренировку? Ваш прогресс будет сохранен.', + quit_no_save: 'Выйти без сохранения', + quit_confirm_title: 'Выйти без сохранения?', + quit_confirm_msg: 'Весь прогресс этой сессии будет потерян. Это действие нельзя отменить.', + confirm: 'Подтвердить', plan_completed: 'План выполнен!', step: 'Шаг', of: 'из', @@ -212,7 +224,7 @@ const translations = { add_weight: 'Доп. вес', // Types - type_strength: 'Силовое', + type_strength: 'Свободные веса и тренажеры', type_bodyweight: 'Свой вес', type_cardio: 'Кардио', type_static: 'Статика',