Edit modals for Sets are complete
This commit is contained in:
@@ -9,6 +9,7 @@ import { getExercises } from '../services/storage';
|
||||
import { Button } from './ui/Button';
|
||||
import { Card } from './ui/Card';
|
||||
import { Modal } from './ui/Modal';
|
||||
import EditSetModal from './EditSetModal';
|
||||
import FilledInput from './FilledInput';
|
||||
|
||||
interface HistoryProps {
|
||||
@@ -25,6 +26,38 @@ const History: React.FC<HistoryProps> = ({ lang }) => {
|
||||
|
||||
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||
const [deletingSetInfo, setDeletingSetInfo] = useState<{ sessionId: string, setId: string } | null>(null);
|
||||
const [editingSetInfo, setEditingSetInfo] = useState<{ sessionId: string, set: WorkoutSet } | null>(null);
|
||||
|
||||
const handleSaveSingleSet = async (updatedSet: WorkoutSet) => {
|
||||
if (!editingSetInfo) return;
|
||||
const session = sessions.find(s => s.id === editingSetInfo.sessionId);
|
||||
if (session) {
|
||||
const updatedSets = session.sets.map(s => s.id === updatedSet.id ? updatedSet : s);
|
||||
const updatedSession = { ...session, sets: updatedSets };
|
||||
try {
|
||||
await updateSession(updatedSession);
|
||||
setEditingSetInfo(null);
|
||||
} catch (e) {
|
||||
console.error("Failed to update set", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveSetFromModal = async (updatedSet: WorkoutSet) => {
|
||||
if (!editingSetInfo) return;
|
||||
|
||||
// If editing within the session edit modal, update local state only
|
||||
if (editingSession && editingSession.id === editingSetInfo.sessionId) {
|
||||
const updatedSets = editingSession.sets.map(s =>
|
||||
s.id === updatedSet.id ? updatedSet : s
|
||||
);
|
||||
setEditingSession({ ...editingSession, sets: updatedSets });
|
||||
setEditingSetInfo(null);
|
||||
} else {
|
||||
// Otherwise save to backend (Quick Log flow)
|
||||
await handleSaveSingleSet(updatedSet);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!userId) return;
|
||||
@@ -87,13 +120,7 @@ const History: React.FC<HistoryProps> = ({ lang }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateSet = (setId: string, field: keyof WorkoutSet, value: number | string) => {
|
||||
if (!editingSession) return;
|
||||
const updatedSets = editingSession.sets.map(s =>
|
||||
s.id === setId ? { ...s, [field]: value } : s
|
||||
);
|
||||
setEditingSession({ ...editingSession, sets: updatedSets });
|
||||
};
|
||||
|
||||
|
||||
const handleDeleteSet = (setId: string) => {
|
||||
if (!editingSession) return;
|
||||
@@ -267,7 +294,7 @@ const History: React.FC<HistoryProps> = ({ lang }) => {
|
||||
// Find the session this set belongs to and open edit mode
|
||||
const parentSession = daySessions.find(s => s.sets.some(st => st.id === set.id));
|
||||
if (parentSession) {
|
||||
setEditingSession(JSON.parse(JSON.stringify(parentSession)));
|
||||
setEditingSetInfo({ sessionId: parentSession.id, set: set });
|
||||
}
|
||||
}}
|
||||
variant="ghost"
|
||||
@@ -380,122 +407,37 @@ const History: React.FC<HistoryProps> = ({ lang }) => {
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-medium text-primary ml-1">{t('sets_count', lang)} ({editingSession.sets.length})</h3>
|
||||
{editingSession.sets.map((set, idx) => (
|
||||
<div key={set.id} className="bg-surface-container-low p-3 rounded-xl border border-outline-variant/20 flex flex-col gap-3">
|
||||
<div className="flex justify-between items-center border-b border-outline-variant/50 pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-6 h-6 rounded-full bg-secondary-container text-on-secondary-container text-xs font-bold flex items-center justify-center">{idx + 1}</span>
|
||||
<span className="font-medium text-on-surface text-sm">{set.exerciseName}{set.side && <span className="ml-2 text-xs font-medium text-on-surface-variant">{t(set.side.toLowerCase() as any, lang)}</span>}</span>
|
||||
<div key={set.id} className="bg-surface-container-low p-3 rounded-xl border border-outline-variant/20 flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="w-5 h-5 rounded-full bg-secondary-container text-on-secondary-container text-[10px] font-bold flex items-center justify-center">{idx + 1}</span>
|
||||
<span className="font-medium text-on-surface text-sm">{set.exerciseName}</span>
|
||||
{set.side && <span className="text-xs text-on-surface-variant font-medium bg-surface-container px-1.5 py-0.5 rounded">{t(set.side.toLowerCase() as any, lang)}</span>}
|
||||
</div>
|
||||
<div className="text-sm text-on-surface-variant pl-7">
|
||||
{formatSetMetrics(set, lang)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
onClick={() => setEditingSetInfo({ sessionId: editingSession.id, set: set })}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-on-surface-variant hover:text-primary"
|
||||
title={t('edit', lang)}
|
||||
>
|
||||
<Pencil size={18} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDeleteSet(set.id)}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 text-on-surface-variant hover:text-error hover:bg-error-container/10"
|
||||
className="text-on-surface-variant hover:text-error hover:bg-error-container/10"
|
||||
title={t('delete', lang)}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
<Trash2 size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{(set.type === ExerciseType.STRENGTH || set.type === ExerciseType.BODYWEIGHT || set.type === ExerciseType.STATIC) && (
|
||||
<div className="bg-surface-container-high rounded px-2 py-1">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Dumbbell size={10} /> {t('weight_kg', lang)}</label>
|
||||
<input
|
||||
type="number"
|
||||
className="w-full bg-transparent text-sm text-on-surface focus:outline-none"
|
||||
value={set.weight === 0 ? '' : (set.weight ?? '')}
|
||||
onChange={(e) => handleUpdateSet(set.id, 'weight', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(set.type === ExerciseType.STRENGTH || set.type === ExerciseType.BODYWEIGHT || set.type === ExerciseType.PLYOMETRIC) && (
|
||||
<div className="bg-surface-container-high rounded px-2 py-1">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Activity size={10} /> {t('reps', lang)}</label>
|
||||
<input
|
||||
type="number"
|
||||
className="w-full bg-transparent text-sm text-on-surface focus:outline-none"
|
||||
value={set.reps === 0 ? '' : (set.reps ?? '')}
|
||||
onChange={(e) => handleUpdateSet(set.id, 'reps', parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(set.type === ExerciseType.BODYWEIGHT || set.type === ExerciseType.STATIC) && (
|
||||
<div className="bg-surface-container-high rounded px-2 py-1">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Percent size={10} /> {t('body_weight_percent', lang)}</label>
|
||||
<input
|
||||
type="number"
|
||||
className="w-full bg-transparent text-sm text-on-surface focus:outline-none"
|
||||
value={set.bodyWeightPercentage === 0 ? '' : (set.bodyWeightPercentage ?? 100)}
|
||||
onChange={(e) => handleUpdateSet(set.id, 'bodyWeightPercentage', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(set.type === ExerciseType.CARDIO || set.type === ExerciseType.STATIC) && (
|
||||
<div className="bg-surface-container-high rounded px-2 py-1">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Timer size={10} /> {t('time_sec', lang)}</label>
|
||||
<input
|
||||
type="number"
|
||||
className="w-full bg-transparent text-sm text-on-surface focus:outline-none"
|
||||
value={set.durationSeconds === 0 ? '' : (set.durationSeconds ?? '')}
|
||||
onChange={(e) => handleUpdateSet(set.id, 'durationSeconds', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(set.type === ExerciseType.CARDIO || set.type === ExerciseType.LONG_JUMP) && (
|
||||
<div className="bg-surface-container-high rounded px-2 py-1">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><ArrowRight size={10} /> {t('dist_m', lang)}</label>
|
||||
<input
|
||||
type="number"
|
||||
className="w-full bg-transparent text-sm text-on-surface focus:outline-none"
|
||||
value={set.distanceMeters === 0 ? '' : (set.distanceMeters ?? '')}
|
||||
onChange={(e) => handleUpdateSet(set.id, 'distanceMeters', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(set.type === ExerciseType.HIGH_JUMP) && (
|
||||
<div className="bg-surface-container-high rounded px-2 py-1">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><ArrowUp size={10} /> {t('height_cm', lang)}</label>
|
||||
<input
|
||||
type="number"
|
||||
className="w-full bg-transparent text-sm text-on-surface focus:outline-none"
|
||||
value={set.height === 0 ? '' : (set.height ?? '')}
|
||||
onChange={(e) => handleUpdateSet(set.id, 'height', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Side Selector - Full width on mobile, 1 col on desktop if space */}
|
||||
{(() => {
|
||||
const exDef = exercises.find(e => e.id === set.exerciseId);
|
||||
const showSide = set.side || exDef?.isUnilateral;
|
||||
|
||||
if (!showSide) return null;
|
||||
|
||||
return (
|
||||
<div className="bg-surface-container-high rounded px-2 py-1 col-span-2 sm:col-span-1 border border-outline-variant/30">
|
||||
<label className="text-[10px] text-on-surface-variant font-bold block mb-1">{t('unilateral', lang)}</label>
|
||||
<div className="flex bg-surface-container-low rounded p-0.5">
|
||||
{(['LEFT', 'ALTERNATELY', 'RIGHT'] as const).map((sideOption) => {
|
||||
const labelMap: Record<string, string> = { LEFT: 'L', RIGHT: 'R', ALTERNATELY: 'A' };
|
||||
return (
|
||||
<button
|
||||
key={sideOption}
|
||||
onClick={() => handleUpdateSet(set.id, 'side', sideOption)}
|
||||
title={t(sideOption.toLowerCase() as any, lang)}
|
||||
className={`flex-1 text-[10px] py-1 rounded transition-colors ${set.side === sideOption
|
||||
? 'bg-primary/10 text-primary font-bold'
|
||||
: 'text-on-surface-variant hover:bg-surface-container'
|
||||
}`}
|
||||
>
|
||||
{labelMap[sideOption]}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -510,6 +452,16 @@ const History: React.FC<HistoryProps> = ({ lang }) => {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
{editingSetInfo && (
|
||||
<EditSetModal
|
||||
isOpen={!!editingSetInfo}
|
||||
onClose={() => setEditingSetInfo(null)}
|
||||
set={editingSetInfo.set}
|
||||
exerciseDef={exercises.find(e => e.id === editingSetInfo.set.exerciseId)}
|
||||
onSave={handleSaveSetFromModal}
|
||||
lang={lang}
|
||||
/>
|
||||
)}
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user