From f169c7c4d3698e77b5d472d8e1419844b2e4c353 Mon Sep 17 00:00:00 2001 From: AG Date: Fri, 12 Dec 2025 21:48:46 +0200 Subject: [PATCH] New exercise name autofill. Log Set button animation. --- src/components/ExerciseModal.tsx | 14 ++++++-- src/components/FilledInput.tsx | 17 ++++++---- src/components/Plans.tsx | 2 +- src/components/Tracker/ActiveSessionView.tsx | 1 + src/components/Tracker/SetLogger.tsx | 34 +++++++++++++++++--- src/components/Tracker/SporadicView.tsx | 1 + tests/user-system-management.spec.ts | 13 +++++++- tests/workout-tracking.spec.ts | 4 +-- 8 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/components/ExerciseModal.tsx b/src/components/ExerciseModal.tsx index 19bb0f3..fb0dea4 100644 --- a/src/components/ExerciseModal.tsx +++ b/src/components/ExerciseModal.tsx @@ -15,15 +15,23 @@ interface ExerciseModalProps { onSave: (exercise: ExerciseDef) => Promise | void; lang: Language; existingExercises?: ExerciseDef[]; + initialName?: string; } -const ExerciseModal: React.FC = ({ isOpen, onClose, onSave, lang, existingExercises = [] }) => { - const [newName, setNewName] = useState(''); +const ExerciseModal: React.FC = ({ isOpen, onClose, onSave, lang, existingExercises = [], initialName = '' }) => { + const [newName, setNewName] = useState(initialName); const [newType, setNewType] = useState(ExerciseType.STRENGTH); const [newBwPercentage, setNewBwPercentage] = useState('100'); const [isUnilateral, setIsUnilateral] = useState(false); const [error, setError] = useState(''); + // Update newName when modal opens or initialName changes + React.useEffect(() => { + if (isOpen) { + setNewName(initialName); + } + }, [isOpen, initialName]); + const exerciseTypeLabels: Record = { [ExerciseType.STRENGTH]: t('type_strength', lang), [ExerciseType.BODYWEIGHT]: t('type_bodyweight', lang), @@ -69,12 +77,12 @@ const ExerciseModal: React.FC = ({ isOpen, onClose, onSave, isOpen={isOpen} onClose={() => { console.log('ExerciseModal onClose'); + setNewName(''); // Reset on close if desired onClose(); }} title={t('create_exercise', lang)} width="md" > - {console.log('ExerciseModal Rendering. isOpen:', isOpen)}
= ({ )} - {rightElement && ( -
- {rightElement} -
- )} -
+ { + rightElement && ( +
+ {rightElement} +
+ ) + } +
); }; diff --git a/src/components/Plans.tsx b/src/components/Plans.tsx index 9d17b23..26639e6 100644 --- a/src/components/Plans.tsx +++ b/src/components/Plans.tsx @@ -50,7 +50,7 @@ interface SortablePlanStepProps { lang: Language; } -const SortablePlanStep = ({ step, index, toggleWeighted, updateRestTime, removeStep, lang }: SortablePlanStepProps) => { +const SortablePlanStep: React.FC = ({ step, index, toggleWeighted, updateRestTime, removeStep, lang }) => { const { attributes, listeners, diff --git a/src/components/Tracker/ActiveSessionView.tsx b/src/components/Tracker/ActiveSessionView.tsx index c40cd28..d750f94 100644 --- a/src/components/Tracker/ActiveSessionView.tsx +++ b/src/components/Tracker/ActiveSessionView.tsx @@ -299,6 +299,7 @@ const ActiveSessionView: React.FC = ({ tracker, activeSe onSave={handleCreateExercise} lang={lang} existingExercises={exercises} + initialName={tracker.searchQuery} /> )} diff --git a/src/components/Tracker/SetLogger.tsx b/src/components/Tracker/SetLogger.tsx index e814ed6..f4598c7 100644 --- a/src/components/Tracker/SetLogger.tsx +++ b/src/components/Tracker/SetLogger.tsx @@ -8,7 +8,7 @@ import { useTracker } from './useTracker'; interface SetLoggerProps { tracker: ReturnType; lang: Language; - onLogSet: () => void; + onLogSet: () => Promise | void; isSporadic?: boolean; } @@ -39,7 +39,31 @@ const SetLogger: React.FC = ({ tracker, lang, onLogSet, isSporad setUnilateralSide } = tracker; + const [localSuccess, setLocalSuccess] = React.useState(false); + + React.useEffect(() => { + if (localSuccess) { + const timer = setTimeout(() => { + setLocalSuccess(false); + }, 750); + return () => clearTimeout(timer); + } + }, [localSuccess]); + + const handleLogClick = async () => { + try { + const result = onLogSet(); + if (result instanceof Promise) { + await result; + } + setLocalSuccess(true); + } catch (error) { + console.error("Failed to log set:", error); + } + }; + const isPlanFinished = activePlan && currentStepIndex >= activePlan.steps.length; + const showSuccess = (isSporadic && sporadicSuccess) || localSuccess; return (
@@ -174,14 +198,14 @@ const SetLogger: React.FC = ({ tracker, lang, onLogSet, isSporad
)} diff --git a/src/components/Tracker/SporadicView.tsx b/src/components/Tracker/SporadicView.tsx index 6cc544d..33e81bc 100644 --- a/src/components/Tracker/SporadicView.tsx +++ b/src/components/Tracker/SporadicView.tsx @@ -153,6 +153,7 @@ const SporadicView: React.FC = ({ tracker, lang }) => { onSave={handleCreateExercise} lang={lang} existingExercises={exercises} + initialName={tracker.searchQuery} /> )} diff --git a/tests/user-system-management.spec.ts b/tests/user-system-management.spec.ts index 9336a28..4a3570f 100644 --- a/tests/user-system-management.spec.ts +++ b/tests/user-system-management.spec.ts @@ -414,6 +414,10 @@ test.describe('V. User & System Management', () => { await expect(blockButton).toBeVisible(); await blockButton.click(); + // Handle Block Confirmation Modal + await expect(page.getByText('Block User?').or(page.getByText('Заблокировать?'))).toBeVisible(); + await page.getByRole('button', { name: /Confirm|Подтвердить/i }).click(); + await expect(userRow.getByText(/Blocked|Block/i)).toBeVisible(); // 5. Verify Blocked User Cannot Login @@ -458,6 +462,10 @@ test.describe('V. User & System Management', () => { const userRowAfter = listContainer.locator('.bg-surface-container-high').filter({ hasText: regularUser.email }).first(); await expect(userRowAfter).toBeVisible(); await userRowAfter.getByRole('button', { name: 'Unblock', exact: true }).click(); + + // Handle Unblock Modal + await expect(page.getByText('Unblock User?').or(page.getByText('Разблокировать?'))).toBeVisible(); + await page.getByRole('button', { name: /Confirm|Подтвердить/i }).click(); // Wait for UI to update (block icon/text should disappear or change style) // Ideally we check API response or UI change. Assuming "Blocked" text goes away or button changes. // The original code checked for not.toBeVisible of blocked text, let's stick to that or button state @@ -594,9 +602,12 @@ test.describe('V. User & System Management', () => { const userRow = listContainer.locator('.bg-surface-container-high').filter({ hasText: userToDelete.email }).first(); await expect(userRow).toBeVisible(); - page.once('dialog', dialog => dialog.accept()); await userRow.getByRole('button', { name: /Delete/i }).click(); + // Handle Delete Confirmation Modal + await expect(page.getByText('Delete User?').or(page.getByText('Удалить пользователя?'))).toBeVisible(); + await page.getByRole('button', { name: /Confirm|Подтвердить/i }).click(); + await expect(page.getByText(userToDelete.email)).not.toBeVisible(); }); }); diff --git a/tests/workout-tracking.spec.ts b/tests/workout-tracking.spec.ts index 120167b..5193d9c 100644 --- a/tests/workout-tracking.spec.ts +++ b/tests/workout-tracking.spec.ts @@ -400,7 +400,7 @@ test.describe('III. Workout Tracking', () => { // Helper to log a set const logSet = async (side: 'L' | 'R' | 'A') => { // Find the logger container (has 'Log Set' button) - const logger = page.locator('div').filter({ has: page.getByRole('button', { name: /Log Set/i }) }).last(); + const logger = page.locator('div').filter({ has: page.getByRole('button', { name: /Log Set|Saved/i }) }).last(); await expect(logger).toBeVisible(); // Select side @@ -416,7 +416,7 @@ test.describe('III. Workout Tracking', () => { // Reps - handle potential multiples if strict, but scoped should be unique await logger.getByLabel('Reps').fill('10'); - await logger.getByRole('button', { name: /Log Set/i }).click(); + await logger.getByRole('button', { name: /Log Set|Saved/i }).click(); }; // Log Left (L)