From 94f0a9a17aa7a9ee7708082e08ff1f14b3e5920b Mon Sep 17 00:00:00 2001 From: AG Date: Wed, 26 Nov 2025 22:26:45 +0200 Subject: [PATCH] Check exercise name uniqueness when creating --- components/ExerciseModal.tsx | 41 +++++++++++++++++++++++++++-------- components/Profile.tsx | 1 + server/prisma/dev.db | Bin 61440 -> 61440 bytes services/i18n.ts | 2 ++ 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/components/ExerciseModal.tsx b/components/ExerciseModal.tsx index 6fd7a62..c2e4bc0 100644 --- a/components/ExerciseModal.tsx +++ b/components/ExerciseModal.tsx @@ -9,12 +9,14 @@ interface ExerciseModalProps { onClose: () => void; onSave: (exercise: ExerciseDef) => Promise | void; lang: Language; + existingExercises?: ExerciseDef[]; } -const ExerciseModal: React.FC = ({ isOpen, onClose, onSave, lang }) => { +const ExerciseModal: React.FC = ({ isOpen, onClose, onSave, lang, existingExercises = [] }) => { const [newName, setNewName] = useState(''); const [newType, setNewType] = useState(ExerciseType.STRENGTH); const [newBwPercentage, setNewBwPercentage] = useState('100'); + const [error, setError] = useState(''); const exerciseTypeLabels: Record = { [ExerciseType.STRENGTH]: t('type_strength', lang), @@ -28,9 +30,21 @@ const ExerciseModal: React.FC = ({ isOpen, onClose, onSave, const handleCreateExercise = async () => { if (!newName.trim()) return; + + // Check for duplicate name (case-insensitive) + const trimmedName = newName.trim(); + const isDuplicate = existingExercises.some( + ex => ex.name.toLowerCase() === trimmedName.toLowerCase() + ); + + if (isDuplicate) { + setError(t('exercise_name_exists', lang) || 'An exercise with this name already exists'); + return; + } + const newEx: ExerciseDef = { id: crypto.randomUUID(), - name: newName.trim(), + name: trimmedName, type: newType, ...(newType === ExerciseType.BODYWEIGHT && { bodyWeightPercentage: parseFloat(newBwPercentage) || 100 }) }; @@ -38,6 +52,7 @@ const ExerciseModal: React.FC = ({ isOpen, onClose, onSave, setNewName(''); setNewType(ExerciseType.STRENGTH); setNewBwPercentage('100'); + setError(''); onClose(); }; @@ -52,13 +67,21 @@ const ExerciseModal: React.FC = ({ isOpen, onClose, onSave,
- setNewName(e.target.value)} - type="text" - autoFocus - /> +
+ { + setNewName(e.target.value); + setError(''); // Clear error when user types + }} + type="text" + autoFocus + /> + {error && ( +

{error}

+ )} +
diff --git a/components/Profile.tsx b/components/Profile.tsx index d57ee72..36e4c33 100644 --- a/components/Profile.tsx +++ b/components/Profile.tsx @@ -514,6 +514,7 @@ const Profile: React.FC = ({ user, onLogout, lang, onLanguageChang onClose={() => setIsCreatingEx(false)} onSave={handleCreateExercise} lang={lang} + existingExercises={exercises} /> )} diff --git a/server/prisma/dev.db b/server/prisma/dev.db index 983d7acbf6107d2aed69fe5042ff3054b64c6409..555bd62af1c779881a80efbf2c375f5c9b2c2a16 100644 GIT binary patch delta 195 zcmZp8z})bFd4e?K?1?hYjI%c;Eb*7(=AO#HKZhrY?*?BXA0zK{UMrqs+&}qkxTkIw z6e#D`P2y$`4V6`7gnzg62JLpG&c(eH=i8?-vmCp&0Gmae3K{iv1k%wCbtH2 QDC6XXvs5r0Gf(8m;e9( delta 82 zcmV-Y0ImOk-~)i*1CSd5nvons0h+O3q)!+M3;6&KoD5