Skip to content

Commit addd926

Browse files
committed
fix: Real-time UI updates and exercise picker - rename routine updates instantly, set +/- reflects immediately, exercise picker shows recent exercises dropdown
1 parent d9efba0 commit addd926

6 files changed

Lines changed: 103 additions & 7 deletions

File tree

src/components/DayView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export function DayView({ date, onBack }: Props) {
6262
key={r.id}
6363
routine={r}
6464
onDelete={deleteRoutine}
65+
onUpdate={refresh}
6566
expanded={activeId === r.id}
6667
onToggle={() => handleToggle(r.id!)}
6768
/>

src/components/DraggableExerciseList.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import type { Exercise } from '../types';
1818
interface Props {
1919
exercises: Exercise[];
2020
onTap: (exercise: Exercise) => void;
21+
onUpdate?: () => void | Promise<void>;
2122
onReorder: (activeId: string, overId: string) => void;
2223
}
2324

24-
export function DraggableExerciseList({ exercises, onTap, onReorder }: Props) {
25+
export function DraggableExerciseList({ exercises, onTap, onUpdate, onReorder }: Props) {
2526
const sensors = useSensors(
2627
useSensor(PointerSensor, {
2728
activationConstraint: {
@@ -60,6 +61,7 @@ export function DraggableExerciseList({ exercises, onTap, onReorder }: Props) {
6061
key={ex.id}
6162
exercise={ex}
6263
onTap={onTap}
64+
onUpdate={onUpdate}
6365
isDraggable={true}
6466
/>
6567
))}

src/components/ExercisePicker.tsx

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { useState } from 'react';
1+
import { useState, useEffect } from 'react';
22
import { createPortal } from 'react-dom';
3+
import { db } from '../db';
34

45
interface Props {
56
onSelect: (exerciseName: string) => void;
@@ -8,16 +9,52 @@ interface Props {
89

910
export function ExercisePicker({ onSelect, onClose }: Props) {
1011
const [name, setName] = useState('');
12+
const [recentExercises, setRecentExercises] = useState<string[]>([]);
13+
const [filteredExercises, setFilteredExercises] = useState<string[]>([]);
14+
15+
useEffect(() => {
16+
// Load recent unique exercise names
17+
async function loadRecent() {
18+
const allExercises = await db.exercises.toArray();
19+
const uniqueNames = Array.from(new Set(allExercises.map(e => e.name)))
20+
.filter(Boolean)
21+
.sort();
22+
setRecentExercises(uniqueNames);
23+
setFilteredExercises(uniqueNames);
24+
}
25+
loadRecent();
26+
}, []);
27+
28+
useEffect(() => {
29+
// Filter exercises based on input
30+
if (!name.trim()) {
31+
setFilteredExercises(recentExercises);
32+
} else {
33+
const filtered = recentExercises.filter(ex =>
34+
ex.toLowerCase().includes(name.toLowerCase())
35+
);
36+
setFilteredExercises(filtered);
37+
}
38+
}, [name, recentExercises]);
1139

1240
function handleSubmit() {
1341
if (name.trim()) {
1442
onSelect(name.trim());
1543
}
1644
}
1745

46+
function handleSelect(exerciseName: string) {
47+
onSelect(exerciseName);
48+
}
49+
1850
function handleKeyDown(e: React.KeyboardEvent) {
1951
if (e.key === 'Enter') {
20-
handleSubmit();
52+
if (filteredExercises.length > 0 && !name.trim()) {
53+
// If no input and there are suggestions, select first one
54+
handleSelect(filteredExercises[0]);
55+
} else {
56+
handleSubmit();
57+
}
2158
}
2259
}
2360

@@ -33,19 +70,34 @@ export function ExercisePicker({ onSelect, onClose }: Props) {
3370
<div className="form-group">
3471
<input
3572
type="text"
36-
placeholder="Exercise name..."
73+
placeholder="Exercise name or search..."
3774
value={name}
3875
onChange={(e) => setName(e.target.value)}
3976
onKeyDown={handleKeyDown}
4077
autoFocus
4178
/>
4279
</div>
80+
81+
{filteredExercises.length > 0 && (
82+
<div className="exercise-suggestions">
83+
<div className="suggestions-label">Recent exercises:</div>
84+
{filteredExercises.map((ex) => (
85+
<div
86+
key={ex}
87+
className="suggestion-item"
88+
onClick={() => handleSelect(ex)}
89+
>
90+
{ex}
91+
</div>
92+
))}
93+
</div>
94+
)}
4395
</div>
4496

4597
<div className="modal-actions">
4698
<button className="btn" onClick={onClose}>Cancel</button>
4799
<button className="btn btn-primary" onClick={handleSubmit} disabled={!name.trim()}>
48-
Add
100+
Add New
49101
</button>
50102
</div>
51103
</div>

src/components/ExerciseRow.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import type { Exercise } from '../types';
66
interface Props {
77
exercise: Exercise;
88
onTap: (exercise: Exercise) => void;
9+
onUpdate?: () => void | Promise<void>;
910
isDraggable?: boolean;
1011
}
1112

12-
export function ExerciseRow({ exercise, onTap, isDraggable = false }: Props) {
13+
export function ExerciseRow({ exercise, onTap, onUpdate, isDraggable = false }: Props) {
1314
const {
1415
attributes,
1516
listeners,
@@ -36,6 +37,7 @@ export function ExerciseRow({ exercise, onTap, isDraggable = false }: Props) {
3637
sets: exercise.sets + 1,
3738
setsCompleted: exercise.setsCompleted + 1,
3839
});
40+
if (onUpdate) await onUpdate();
3941
}
4042

4143
async function toggleSet(index: number, e: React.MouseEvent) {
@@ -44,6 +46,7 @@ export function ExerciseRow({ exercise, onTap, isDraggable = false }: Props) {
4446
? index
4547
: index + 1;
4648
await db.exercises.update(exercise.id!, { setsCompleted: newCompleted });
49+
if (onUpdate) await onUpdate();
4750
}
4851

4952
const style = {

src/components/RoutineCard.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import type { Routine, Exercise } from '../types';
1010
interface Props {
1111
routine: Routine;
1212
onDelete: (id: string) => void;
13+
onUpdate?: () => void | Promise<void>;
1314
expanded: boolean;
1415
onToggle: () => void;
1516
}
1617

17-
export function RoutineCard({ routine, onDelete, expanded, onToggle }: Props) {
18+
export function RoutineCard({ routine, onDelete, onUpdate, expanded, onToggle }: Props) {
1819
const [editing, setEditing] = useState<Exercise | null>(null);
1920
const [renaming, setRenaming] = useState(false);
2021
const [showCopyDialog, setShowCopyDialog] = useState(false);
@@ -50,6 +51,7 @@ export function RoutineCard({ routine, onDelete, expanded, onToggle }: Props) {
5051
setRenaming(false);
5152
if (newName.trim() && newName !== routine.name) {
5253
await db.routines.update(routine.id!, { name: newName.trim() });
54+
if (onUpdate) await onUpdate();
5355
}
5456
}
5557

@@ -158,6 +160,7 @@ export function RoutineCard({ routine, onDelete, expanded, onToggle }: Props) {
158160
<DraggableExerciseList
159161
exercises={exercises}
160162
onTap={setEditing}
163+
onUpdate={refresh}
161164
onReorder={handleReorder}
162165
/>
163166
)}

src/styles/app.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,3 +709,38 @@ body {
709709
margin-left: 8px;
710710
white-space: nowrap;
711711
}
712+
713+
714+
/* Exercise Suggestions */
715+
.exercise-suggestions {
716+
display: flex;
717+
flex-direction: column;
718+
gap: 4px;
719+
max-height: 300px;
720+
overflow-y: auto;
721+
}
722+
723+
.suggestions-label {
724+
font-size: 0.75rem;
725+
color: var(--text-muted);
726+
margin-bottom: 4px;
727+
padding: 0 4px;
728+
}
729+
730+
.suggestion-item {
731+
padding: 10px 12px;
732+
background: var(--surface-2);
733+
border-radius: 6px;
734+
cursor: pointer;
735+
transition: background 0.2s;
736+
font-size: 0.9rem;
737+
}
738+
739+
.suggestion-item:hover {
740+
background: var(--accent);
741+
color: var(--bg);
742+
}
743+
744+
.suggestion-item:active {
745+
transform: scale(0.98);
746+
}

0 commit comments

Comments
 (0)