All tests fixed. Deployment on NAS prepared

This commit is contained in:
aodulov
2025-12-18 07:29:35 +02:00
parent 9cb0d66455
commit 97b4e5de32
37 changed files with 1303 additions and 2083 deletions

View File

@@ -95,10 +95,37 @@ const EditSetModal: React.FC<EditSetModalProps> = ({
<h3 className="text-lg font-medium text-on-surface mb-1">
{set.exerciseName}
</h3>
<p className="text-sm text-on-surface-variant">
<p className="text-sm text-on-surface-variant mb-2">
{exerciseDef?.type || set.type}
{set.side && `${t(set.side.toLowerCase() as any, lang)}`}
</p>
{set.side && (
<div className="flex items-center gap-2 bg-surface-container rounded-full p-1 w-fit">
<button
onClick={() => handleUpdate('side', 'LEFT')}
title={t('left', lang)}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${set.side === 'LEFT' ? 'bg-primary-container text-on-primary-container' : 'text-on-surface-variant hover:bg-surface-container-high'
}`}
>
L
</button>
<button
onClick={() => handleUpdate('side', 'ALTERNATELY')}
title={t('alternately', lang)}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${set.side === 'ALTERNATELY' ? 'bg-tertiary-container text-on-tertiary-container' : 'text-on-surface-variant hover:bg-surface-container-high'
}`}
>
A
</button>
<button
onClick={() => handleUpdate('side', 'RIGHT')}
title={t('right', lang)}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${set.side === 'RIGHT' ? 'bg-secondary-container text-on-secondary-container' : 'text-on-surface-variant hover:bg-surface-container-high'
}`}
>
R
</button>
</div>
)}
</div>
{/* Date & Time */}

View File

@@ -318,6 +318,7 @@ const History: React.FC<HistoryProps> = ({ lang }) => {
variant="ghost"
size="icon"
className="text-on-surface-variant hover:text-primary"
aria-label={t('edit', lang)}
>
<Pencil size={24} />
</Button>
@@ -332,6 +333,7 @@ const History: React.FC<HistoryProps> = ({ lang }) => {
variant="ghost"
size="icon"
className="text-error hover:text-error"
aria-label={t('delete', lang)}
>
<Trash2 size={24} />
</Button>

View File

@@ -123,7 +123,7 @@ const SortablePlanStep: React.FC<SortablePlanStepProps> = ({ step, index, toggle
};
return (
<div ref={setNodeRef} style={style} {...attributes}>
<div ref={setNodeRef} style={style} {...attributes} data-testid="plan-exercise-item">
<Card
className={`flex items-center gap-3 transition-all hover:bg-surface-container-high ${isDragging ? 'bg-surface-container-high shadow-elevation-3' : ''}`}
>
@@ -666,35 +666,7 @@ const Plans: React.FC<PlansProps> = ({ lang }) => {
</SideSheet>
<SideSheet
isOpen={showExerciseSelector}
onClose={() => setShowExerciseSelector(false)}
title={t('select_exercise', lang)}
width="lg"
>
<div className="flex flex-col h-[60vh]">
<div className="flex justify-end mb-2">
<Button onClick={() => setIsCreatingExercise(true)} variant="ghost" className="text-primary hover:bg-primary-container/20 flex gap-2">
<Plus size={18} /> {t('create_exercise', lang)}
</Button>
</div>
<div className="flex-1 overflow-y-auto -mx-6 px-6">
{availableExercises
.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map(ex => (
<button
key={ex.id}
onClick={() => addStep(ex)}
className="w-full text-left p-4 border-b border-outline-variant hover:bg-surface-container-high text-on-surface flex justify-between group"
>
<span className="group-hover:text-primary transition-colors">{ex.name}</span>
<span className="text-xs bg-secondary-container text-on-secondary-container px-2 py-1 rounded-full">{ex.type}</span>
</button>
))}
</div>
</div>
</SideSheet>
<ExerciseModal
isOpen={isCreatingExercise}

View File

@@ -35,18 +35,26 @@ export const useRestTimer = ({ defaultTime, onFinish }: UseRestTimerProps) => {
let initialDuration = defaultTime;
if (savedState) {
initialDuration = savedState.duration || defaultTime;
initialStatus = savedState.status;
initialTimeLeft = savedState.timeLeft;
// Only restore if running OR if the context (defaultTime) matches
// This prevents carrying over timer state between different plan steps or sessions
// where the default time is different.
const contextMatch = savedState.defaultTimeSnapshot === defaultTime;
const isRunning = savedState.status === 'RUNNING';
if (initialStatus === 'RUNNING' && savedState.endTime) {
const now = Date.now();
const remaining = Math.max(0, Math.ceil((savedState.endTime - now) / 1000));
if (remaining > 0) {
initialTimeLeft = remaining;
} else {
initialStatus = 'FINISHED'; // It finished while we were away
initialTimeLeft = 0;
if (isRunning || contextMatch) {
initialDuration = savedState.duration || defaultTime;
initialStatus = savedState.status;
initialTimeLeft = savedState.timeLeft;
if (initialStatus === 'RUNNING' && savedState.endTime) {
const now = Date.now();
const remaining = Math.max(0, Math.ceil((savedState.endTime - now) / 1000));
if (remaining > 0) {
initialTimeLeft = remaining;
} else {
initialStatus = 'FINISHED'; // It finished while we were away
initialTimeLeft = 0;
}
}
}
}
@@ -90,10 +98,11 @@ export const useRestTimer = ({ defaultTime, onFinish }: UseRestTimerProps) => {
status,
timeLeft,
duration,
endTime: endTimeRef.current
endTime: endTimeRef.current,
defaultTimeSnapshot: defaultTime // Save context
};
localStorage.setItem('gymflow_rest_timer', JSON.stringify(stateToSave));
}, [status, timeLeft, duration]);
}, [status, timeLeft, duration, defaultTime]);
// Update internal duration when defaultTime changes
useEffect(() => {

View File

@@ -125,7 +125,7 @@ const translations = {
my_plans: 'My Plans',
no_plans_yet: 'No workout plans yet.',
ask_ai_to_create: 'Ask your AI coach to create one',
create_manually: 'Create one manually',
create_manually: 'Manually',
create_with_ai: 'With AI',
ai_plan_prompt_title: 'Create Plan with AI',
ai_plan_prompt_placeholder: 'Any specific requirements? (optional)',

View File

@@ -32,6 +32,7 @@ export const getSessions = async (userId: string): Promise<WorkoutSession[]> =>
endTime: session.endTime ? new Date(session.endTime).getTime() : undefined,
sets: session.sets.map((set) => ({
...set,
timestamp: new Date(set.timestamp).getTime(),
exerciseName: set.exerciseName || set.exercise?.name || 'Unknown',
type: set.type || set.exercise?.type || ExerciseType.STRENGTH
})) as WorkoutSet[]
@@ -60,6 +61,7 @@ export const getActiveSession = async (userId: string): Promise<WorkoutSession |
endTime: session.endTime ? new Date(session.endTime).getTime() : undefined,
sets: session.sets.map((set) => ({
...set,
timestamp: new Date(set.timestamp).getTime(),
exerciseName: set.exerciseName || set.exercise?.name || 'Unknown',
type: set.type || set.exercise?.type || ExerciseType.STRENGTH
})) as WorkoutSet[]