Exercise Creation from Plan fixed
This commit is contained in:
@@ -15,6 +15,7 @@ import { Card } from './ui/Card';
|
|||||||
import { Modal } from './ui/Modal';
|
import { Modal } from './ui/Modal';
|
||||||
import { SideSheet } from './ui/SideSheet';
|
import { SideSheet } from './ui/SideSheet';
|
||||||
import { Checkbox } from './ui/Checkbox';
|
import { Checkbox } from './ui/Checkbox';
|
||||||
|
import ExerciseModal from './ExerciseModal';
|
||||||
|
|
||||||
interface PlansProps {
|
interface PlansProps {
|
||||||
lang: Language;
|
lang: Language;
|
||||||
@@ -42,9 +43,6 @@ const Plans: React.FC<PlansProps> = ({ lang }) => {
|
|||||||
|
|
||||||
// Create Exercise State
|
// Create Exercise State
|
||||||
const [isCreatingExercise, setIsCreatingExercise] = useState(false);
|
const [isCreatingExercise, setIsCreatingExercise] = useState(false);
|
||||||
const [newExName, setNewExName] = useState('');
|
|
||||||
const [newExType, setNewExType] = useState<ExerciseType>(ExerciseType.STRENGTH);
|
|
||||||
const [newExBwPercentage, setNewExBwPercentage] = useState<string>('100');
|
|
||||||
|
|
||||||
// Preparation Modal State
|
// Preparation Modal State
|
||||||
const [showPlanPrep, setShowPlanPrep] = useState<WorkoutPlan | null>(null);
|
const [showPlanPrep, setShowPlanPrep] = useState<WorkoutPlan | null>(null);
|
||||||
@@ -123,37 +121,16 @@ const Plans: React.FC<PlansProps> = ({ lang }) => {
|
|||||||
setShowExerciseSelector(false);
|
setShowExerciseSelector(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateExercise = async () => {
|
const handleSaveNewExercise = async (newEx: ExerciseDef) => {
|
||||||
if (!newExName.trim()) return;
|
|
||||||
const newEx: ExerciseDef = {
|
|
||||||
id: generateId(),
|
|
||||||
name: newExName.trim(),
|
|
||||||
type: newExType,
|
|
||||||
...(newExType === ExerciseType.BODYWEIGHT && { bodyWeightPercentage: parseFloat(newExBwPercentage) || 100 })
|
|
||||||
};
|
|
||||||
await saveExercise(userId, newEx);
|
await saveExercise(userId, newEx);
|
||||||
const exList = await getExercises(userId);
|
const exList = await getExercises(userId);
|
||||||
setAvailableExercises(exList.filter(e => !e.isArchived));
|
setAvailableExercises(exList.filter(e => !e.isArchived));
|
||||||
|
|
||||||
// Automatically add the new exercise to the plan
|
// Automatically add the new exercise to the plan
|
||||||
addStep(newEx);
|
addStep(newEx);
|
||||||
|
|
||||||
setNewExName('');
|
|
||||||
setNewExType(ExerciseType.STRENGTH);
|
|
||||||
setNewExBwPercentage('100');
|
|
||||||
setIsCreatingExercise(false);
|
setIsCreatingExercise(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const exerciseTypeLabels: Record<ExerciseType, string> = {
|
|
||||||
[ExerciseType.STRENGTH]: t('type_strength', lang),
|
|
||||||
[ExerciseType.BODYWEIGHT]: t('type_bodyweight', lang),
|
|
||||||
[ExerciseType.CARDIO]: t('type_cardio', lang),
|
|
||||||
[ExerciseType.STATIC]: t('type_static', lang),
|
|
||||||
[ExerciseType.HIGH_JUMP]: t('type_height', lang),
|
|
||||||
[ExerciseType.LONG_JUMP]: t('type_dist', lang),
|
|
||||||
[ExerciseType.PLYOMETRIC]: t('type_jump', lang),
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleWeighted = (stepId: string) => {
|
const toggleWeighted = (stepId: string) => {
|
||||||
setSteps(steps.map(s => s.id === stepId ? { ...s, isWeighted: !s.isWeighted } : s));
|
setSteps(steps.map(s => s.id === stepId ? { ...s, isWeighted: !s.isWeighted } : s));
|
||||||
};
|
};
|
||||||
@@ -328,70 +305,13 @@ const Plans: React.FC<PlansProps> = ({ lang }) => {
|
|||||||
</div>
|
</div>
|
||||||
</SideSheet>
|
</SideSheet>
|
||||||
|
|
||||||
<SideSheet
|
<ExerciseModal
|
||||||
isOpen={isCreatingExercise}
|
isOpen={isCreatingExercise}
|
||||||
onClose={() => setIsCreatingExercise(false)}
|
onClose={() => setIsCreatingExercise(false)}
|
||||||
title={t('create_exercise', lang)}
|
onSave={handleSaveNewExercise}
|
||||||
width="md"
|
lang={lang}
|
||||||
>
|
existingExercises={availableExercises}
|
||||||
<div className="space-y-6 pt-2">
|
|
||||||
<FilledInput
|
|
||||||
label={t('ex_name', lang)}
|
|
||||||
value={newExName}
|
|
||||||
onChange={(e: any) => setNewExName(e.target.value)}
|
|
||||||
type="text"
|
|
||||||
autoFocus
|
|
||||||
autocapitalize="words"
|
|
||||||
onBlur={() => setNewExName(toTitleCase(newExName))}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs text-on-surface-variant font-medium mb-3">{t('ex_type', lang)}</label>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{[
|
|
||||||
{ id: ExerciseType.STRENGTH, label: exerciseTypeLabels[ExerciseType.STRENGTH], icon: Dumbbell },
|
|
||||||
{ id: ExerciseType.BODYWEIGHT, label: exerciseTypeLabels[ExerciseType.BODYWEIGHT], icon: User },
|
|
||||||
{ id: ExerciseType.CARDIO, label: exerciseTypeLabels[ExerciseType.CARDIO], icon: Flame },
|
|
||||||
{ id: ExerciseType.STATIC, label: exerciseTypeLabels[ExerciseType.STATIC], icon: TimerIcon },
|
|
||||||
{ id: ExerciseType.HIGH_JUMP, label: exerciseTypeLabels[ExerciseType.HIGH_JUMP], icon: ArrowUp },
|
|
||||||
{ id: ExerciseType.LONG_JUMP, label: exerciseTypeLabels[ExerciseType.LONG_JUMP], icon: Ruler },
|
|
||||||
{ id: ExerciseType.PLYOMETRIC, label: exerciseTypeLabels[ExerciseType.PLYOMETRIC], icon: Footprints },
|
|
||||||
].map((type) => (
|
|
||||||
<button
|
|
||||||
key={type.id}
|
|
||||||
onClick={() => setNewExType(type.id)}
|
|
||||||
className={`px-4 py-2 rounded-lg flex items-center gap-2 text-xs font-medium border transition-all ${newExType === type.id
|
|
||||||
? 'bg-secondary-container text-on-secondary-container border-transparent'
|
|
||||||
: 'bg-transparent text-on-surface-variant border-outline hover:border-on-surface-variant'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<type.icon size={14} /> {type.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{newExType === ExerciseType.BODYWEIGHT && (
|
|
||||||
<FilledInput
|
|
||||||
label={t('body_weight_percent', lang)}
|
|
||||||
value={newExBwPercentage}
|
|
||||||
onChange={(e: any) => setNewExBwPercentage(e.target.value)}
|
|
||||||
icon={<Percent size={12} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex justify-end mt-6">
|
|
||||||
<Button
|
|
||||||
onClick={handleCreateExercise}
|
|
||||||
fullWidth
|
|
||||||
size="lg"
|
|
||||||
>
|
|
||||||
<CheckCircle size={20} className="mr-2" />
|
|
||||||
{t('create_btn', lang)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SideSheet>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
|||||||
const [isCreatingEx, setIsCreatingEx] = useState(false);
|
const [isCreatingEx, setIsCreatingEx] = useState(false);
|
||||||
const [exerciseNameFilter, setExerciseNameFilter] = useState('');
|
const [exerciseNameFilter, setExerciseNameFilter] = useState('');
|
||||||
|
|
||||||
|
// Admin Confirmation Modal State
|
||||||
|
type AdminActionType = 'DELETE_USER' | 'BLOCK_USER' | 'UNBLOCK_USER';
|
||||||
|
const [confirmAction, setConfirmAction] = useState<{ type: AdminActionType, userId: string, email?: string, currentBlockState?: boolean } | null>(null);
|
||||||
|
|
||||||
const exerciseTypeLabels: Record<ExerciseType, string> = {
|
const exerciseTypeLabels: Record<ExerciseType, string> = {
|
||||||
[ExerciseType.STRENGTH]: t('type_strength', lang),
|
[ExerciseType.STRENGTH]: t('type_strength', lang),
|
||||||
[ExerciseType.BODYWEIGHT]: t('type_bodyweight', lang),
|
[ExerciseType.BODYWEIGHT]: t('type_bodyweight', lang),
|
||||||
@@ -191,16 +195,32 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdminDeleteUser = async (uid: string) => {
|
const handleAdminDeleteUser = (uid: string, email: string) => {
|
||||||
if (confirm(t('delete_confirm', lang))) {
|
setConfirmAction({ type: 'DELETE_USER', userId: uid, email });
|
||||||
await deleteUser(uid);
|
|
||||||
await refreshUserList();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdminBlockUser = async (uid: string, isBlocked: boolean) => {
|
const handleAdminBlockUser = (uid: string, isBlocked: boolean, email: string) => {
|
||||||
await toggleBlockUser(uid, isBlocked);
|
setConfirmAction({
|
||||||
|
type: isBlocked ? 'BLOCK_USER' : 'UNBLOCK_USER',
|
||||||
|
userId: uid,
|
||||||
|
email,
|
||||||
|
currentBlockState: isBlocked
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmAction = async () => {
|
||||||
|
if (!confirmAction) return;
|
||||||
|
|
||||||
|
if (confirmAction.type === 'DELETE_USER') {
|
||||||
|
await deleteUser(confirmAction.userId);
|
||||||
await refreshUserList();
|
await refreshUserList();
|
||||||
|
} else if (confirmAction.type === 'BLOCK_USER' || confirmAction.type === 'UNBLOCK_USER') {
|
||||||
|
// If type is BLOCK_USER, we are passing true to block. If UNBLOCK, false.
|
||||||
|
// But simpler: we stored currentBlockState which is the target state (e.g. isBlocked=true passed to handler)
|
||||||
|
await toggleBlockUser(confirmAction.userId, confirmAction.currentBlockState!);
|
||||||
|
await refreshUserList();
|
||||||
|
}
|
||||||
|
setConfirmAction(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdminResetPass = async (uid: string) => {
|
const handleAdminResetPass = async (uid: string) => {
|
||||||
@@ -551,7 +571,7 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
|||||||
{u.role !== 'ADMIN' && (
|
{u.role !== 'ADMIN' && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleAdminBlockUser(u.id, !u.isBlocked)}
|
onClick={() => handleAdminBlockUser(u.id, !u.isBlocked, u.email)}
|
||||||
className={`p-2 rounded-full ${u.isBlocked ? 'bg-primary/20 text-primary' : 'text-on-surface-variant hover:bg-white/10'}`}
|
className={`p-2 rounded-full ${u.isBlocked ? 'bg-primary/20 text-primary' : 'text-on-surface-variant hover:bg-white/10'}`}
|
||||||
title={u.isBlocked ? t('unblock', lang) : t('block', lang)}
|
title={u.isBlocked ? t('unblock', lang) : t('block', lang)}
|
||||||
aria-label={u.isBlocked ? t('unblock', lang) : t('block', lang)}
|
aria-label={u.isBlocked ? t('unblock', lang) : t('block', lang)}
|
||||||
@@ -559,7 +579,7 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
|||||||
<Ban size={18} />
|
<Ban size={18} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleAdminDeleteUser(u.id)}
|
onClick={() => handleAdminDeleteUser(u.id, u.email)}
|
||||||
className="p-2 text-on-surface-variant hover:text-error hover:bg-error/10 rounded-full"
|
className="p-2 text-on-surface-variant hover:text-error hover:bg-error/10 rounded-full"
|
||||||
title={t('delete', lang)}
|
title={t('delete', lang)}
|
||||||
aria-label={t('delete', lang)}
|
aria-label={t('delete', lang)}
|
||||||
@@ -649,6 +669,41 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Admin Confirmation Modal */}
|
||||||
|
<Modal
|
||||||
|
isOpen={!!confirmAction}
|
||||||
|
onClose={() => setConfirmAction(null)}
|
||||||
|
title={
|
||||||
|
confirmAction?.type === 'DELETE_USER' ? t('delete_user_confirm_title', lang) :
|
||||||
|
confirmAction?.type === 'BLOCK_USER' ? t('block_confirm_title', lang) :
|
||||||
|
t('unblock_confirm_title', lang)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-on-surface-variant">
|
||||||
|
{
|
||||||
|
confirmAction?.type === 'DELETE_USER' ? t('delete_user_confirm_msg', lang) :
|
||||||
|
confirmAction?.type === 'BLOCK_USER' ? t('block_confirm_msg', lang) :
|
||||||
|
t('unblock_confirm_msg', lang)
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
{confirmAction?.email && (
|
||||||
|
<p className="font-medium text-on-surface">{confirmAction.email}</p>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-end gap-2 mt-4">
|
||||||
|
<Button onClick={() => setConfirmAction(null)} variant="ghost">
|
||||||
|
{t('cancel', lang)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleConfirmAction}
|
||||||
|
variant={confirmAction?.type === 'UNBLOCK_USER' ? 'primary' : 'destructive'}
|
||||||
|
>
|
||||||
|
{t('confirm', lang)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
isOpen={snackbar.isOpen}
|
isOpen={snackbar.isOpen}
|
||||||
|
|||||||
@@ -156,6 +156,12 @@ const translations = {
|
|||||||
reset_pass: 'Reset Pass',
|
reset_pass: 'Reset Pass',
|
||||||
delete_account: 'Delete Account',
|
delete_account: 'Delete Account',
|
||||||
delete_account_confirm: 'Are you sure? All your data (sessions, plans) will be permanently deleted.',
|
delete_account_confirm: 'Are you sure? All your data (sessions, plans) will be permanently deleted.',
|
||||||
|
delete_user_confirm_title: 'Delete User?',
|
||||||
|
delete_user_confirm_msg: 'Are you sure you want to delete this user? This action cannot be undone.',
|
||||||
|
block_confirm_title: 'Block User?',
|
||||||
|
block_confirm_msg: 'Are you sure you want to block this user? They will not be able to login.',
|
||||||
|
unblock_confirm_title: 'Unblock User?',
|
||||||
|
unblock_confirm_msg: 'Are you sure you want to unblock this user?',
|
||||||
user_deleted: 'User deleted',
|
user_deleted: 'User deleted',
|
||||||
pass_reset: 'Password reset',
|
pass_reset: 'Password reset',
|
||||||
manage_exercises: 'Manage Exercises',
|
manage_exercises: 'Manage Exercises',
|
||||||
@@ -330,6 +336,12 @@ const translations = {
|
|||||||
reset_pass: 'Сброс пароля',
|
reset_pass: 'Сброс пароля',
|
||||||
delete_account: 'Удалить аккаунт',
|
delete_account: 'Удалить аккаунт',
|
||||||
delete_account_confirm: 'Вы уверены? Все ваши данные (сессии, планы) будут безвозвратно удалены.',
|
delete_account_confirm: 'Вы уверены? Все ваши данные (сессии, планы) будут безвозвратно удалены.',
|
||||||
|
delete_user_confirm_title: 'Удалить пользователя?',
|
||||||
|
delete_user_confirm_msg: 'Вы уверены? Это действие нельзя отменить.',
|
||||||
|
block_confirm_title: 'Заблокировать?',
|
||||||
|
block_confirm_msg: 'Пользователь не сможет войти в систему.',
|
||||||
|
unblock_confirm_title: 'Разблокировать?',
|
||||||
|
unblock_confirm_msg: 'Разблокировать доступ пользователю?',
|
||||||
user_deleted: 'Пользователь удален',
|
user_deleted: 'Пользователь удален',
|
||||||
pass_reset: 'Пароль сброшен',
|
pass_reset: 'Пароль сброшен',
|
||||||
manage_exercises: 'Управление упражнениями',
|
manage_exercises: 'Управление упражнениями',
|
||||||
|
|||||||
Reference in New Issue
Block a user