Skip to content

Commit c03ff14

Browse files
Copilotgfauredev
andauthored
feat: past session detail view on click; restore cancelled pending exercises
Agent-Logs-Url: https://github.com/gfauredev/LogOut/sessions/ce75a3ab-7b9c-4b14-bbf1-aa931d8aada5 Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
1 parent 5d865ad commit c03ff14

4 files changed

Lines changed: 94 additions & 11 deletions

File tree

src/components/home.rs

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler<String>) -> Elem
238238
const MAX_VISIBLE: usize = 9;
239239
let mut show_all_exercises = use_signal(|| false);
240240
let mut show_notes = use_signal(|| false);
241+
let mut show_detail = use_signal(|| false);
241242
let session_id = session.id.clone();
242243
let has_notes = !session.notes.is_empty();
243244
let session_notes = session.notes.clone();
@@ -299,8 +300,26 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler<String>) -> Elem
299300
total_unique.min(MAX_VISIBLE)
300301
};
301302
let hidden_count = total_unique.saturating_sub(visible_count);
303+
// Resolved exercise logs for the detail view (name + metrics, reverse chronological).
304+
let resolved_logs: Vec<(String, crate::models::ExerciseLog)> = {
305+
let all = all_exercises.read();
306+
let custom = custom_exercises.read();
307+
let lang = lang_str.read();
308+
session
309+
.exercise_logs
310+
.iter()
311+
.map(|log| {
312+
let name = exercise_db::resolve_exercise(&all, &custom, &log.exercise_id)
313+
.map_or_else(
314+
|| log.exercise_name.clone(),
315+
|ex| ex.name_for_lang(&lang).to_owned(),
316+
);
317+
(name, log.clone())
318+
})
319+
.collect()
320+
};
302321
rsx! {
303-
article {
322+
article { onclick: move |_| show_detail.toggle(),
304323
header {
305324
time { "{date_str}" }
306325
div {
@@ -312,7 +331,8 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler<String>) -> Elem
312331
class: "edit",
313332
onclick: {
314333
let pending_ids = pending_ids.clone();
315-
move |_| {
334+
move |evt: Event<MouseData>| {
335+
evt.stop_propagation();
316336
let mut new_session = WorkoutSession::new();
317337
new_session.pending_exercise_ids.clone_from(&pending_ids);
318338
storage::save_session(new_session);
@@ -322,12 +342,14 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler<String>) -> Elem
322342
"🔁"
323343
}
324344
}
325-
HoldDeleteButton {
326-
title: t!("session-delete-title").to_string(),
327-
on_delete: move |()| {
328-
storage::delete_session(&session_id);
329-
on_delete.call(session_id.clone());
330-
},
345+
span { onclick: move |evt| evt.stop_propagation(),
346+
HoldDeleteButton {
347+
title: t!("session-delete-title").to_string(),
348+
on_delete: move |()| {
349+
storage::delete_session(&session_id);
350+
on_delete.call(session_id.clone());
351+
},
352+
}
331353
}
332354
}
333355
if !unique_exercises.is_empty() {
@@ -337,7 +359,8 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler<String>) -> Elem
337359
class: "{tag_class}",
338360
onclick: {
339361
let name = name.clone();
340-
move |_| {
362+
move |evt: Event<MouseData>| {
363+
evt.stop_propagation();
341364
search_signal.set(Some(name.clone()));
342365
navigator.push(Route::Exercises {});
343366
}
@@ -348,19 +371,48 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler<String>) -> Elem
348371
if hidden_count > 0 {
349372
li {
350373
class: "more",
351-
onclick: move |_| show_all_exercises.set(true),
374+
onclick: move |evt: Event<MouseData>| {
375+
evt.stop_propagation();
376+
show_all_exercises.set(true);
377+
},
352378
{t!("session-show-more", count : hidden_count.to_string())}
353379
}
354380
}
355381
}
356382
}
383+
if *show_detail.read() {
384+
for (idx, (name, log)) in resolved_logs.iter().enumerate().rev() {
385+
article { key: "{idx}",
386+
header {
387+
h4 { "{name}" }
388+
}
389+
ul {
390+
if log.weight_hg.0 > 0 {
391+
li { "{log.weight_hg}" }
392+
}
393+
if let Some(reps) = log.reps {
394+
li { "{reps} reps" }
395+
}
396+
if let Some(d) = log.distance_m {
397+
li { "{d}" }
398+
}
399+
if let Some(dur) = log.duration_seconds() {
400+
li { "{crate::models::format_time(dur)}" }
401+
}
402+
}
403+
}
404+
}
405+
}
357406
if has_notes {
358407
if *show_notes.read() {
359408
div { dangerous_inner_html: "{markdown_to_html(&session_notes)}" }
360409
} else {
361410
button {
362411
title: t!("session-notes-unfold"),
363-
onclick: move |_| show_notes.set(true),
412+
onclick: move |evt: Event<MouseData>| {
413+
evt.stop_propagation();
414+
show_notes.set(true);
415+
},
364416
"📝"
365417
}
366418
}

src/models/session.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ pub struct WorkoutSession {
3939
#[serde(default)]
4040
/// Free-form session notes written by the user (Markdown supported).
4141
pub notes: String,
42+
#[serde(default)]
43+
/// Whether the currently-active exercise was started from the pre-added
44+
/// (pending) list. Used by `cancel_exercise_in_session` to decide whether
45+
/// to put the exercise back into `pending_exercise_ids`.
46+
pub current_exercise_from_pending: bool,
4247
}
4348
impl WorkoutSession {
4449
/// Create a new session with current timestamp and a unique ID.
@@ -56,6 +61,7 @@ impl WorkoutSession {
5661
paused_at: None,
5762
total_paused_duration: 0,
5863
notes: String::new(),
64+
current_exercise_from_pending: false,
5965
}
6066
}
6167
/// Returns true if the session is currently active (no end time).
@@ -157,6 +163,7 @@ mod tests {
157163
paused_at: None,
158164
total_paused_duration: 0,
159165
notes: String::new(),
166+
current_exercise_from_pending: false,
160167
};
161168
let json = serde_json::to_string(&session).unwrap();
162169
let back: WorkoutSession = serde_json::from_str(&json).unwrap();
@@ -178,6 +185,7 @@ mod tests {
178185
paused_at: None,
179186
total_paused_duration: 0,
180187
notes: String::new(),
188+
current_exercise_from_pending: false,
181189
};
182190
let json = serde_json::to_string(&session).unwrap();
183191
let back: WorkoutSession = serde_json::from_str(&json).unwrap();
@@ -206,6 +214,7 @@ mod tests {
206214
paused_at: None,
207215
total_paused_duration: 0,
208216
notes: String::new(),
217+
current_exercise_from_pending: false,
209218
};
210219
assert_eq!(s.duration_seconds(), 1000);
211220
s.paused_at = Some(1500);
@@ -226,6 +235,7 @@ mod tests {
226235
paused_at: Some(1500),
227236
total_paused_duration: 0,
228237
notes: String::new(),
238+
current_exercise_from_pending: false,
229239
};
230240
// Simulate resume at t=1700: pause_duration = 200s
231241
// Manually set total_paused_duration as resume() uses get_current_timestamp()

src/services/app_state.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ pub fn begin_exercise_in_session(exercise_id: String, exercise_start: u64) {
333333
updated.rest_start_time = None;
334334
updated.current_exercise_id = Some(exercise_id);
335335
updated.current_exercise_start = Some(exercise_start);
336+
updated.current_exercise_from_pending = false;
336337
save_session(updated);
337338
}
338339
/// Append a completed exercise log to the active session and start the rest timer.
@@ -362,6 +363,7 @@ pub fn append_exercise_log(log: ExerciseLog) {
362363
updated.rest_start_time = Some(get_current_timestamp());
363364
updated.current_exercise_id = None;
364365
updated.current_exercise_start = None;
366+
updated.current_exercise_from_pending = false;
365367
save_session(updated);
366368
}
367369
/// Discard the in-progress exercise in the active session (no log is written).
@@ -374,8 +376,14 @@ pub fn cancel_exercise_in_session() {
374376
return;
375377
};
376378
let mut updated = session;
379+
if updated.current_exercise_from_pending {
380+
if let Some(ref ex_id) = updated.current_exercise_id.clone() {
381+
updated.pending_exercise_ids.insert(0, ex_id.clone());
382+
}
383+
}
377384
updated.current_exercise_id = None;
378385
updated.current_exercise_start = None;
386+
updated.current_exercise_from_pending = false;
379387
save_session(updated);
380388
}
381389
/// Remove `exercise_id` from the pending list and make it the active exercise.
@@ -402,6 +410,7 @@ pub fn start_pending_exercise_in_session(exercise_id: String, exercise_start: u6
402410
updated.rest_start_time = None;
403411
updated.current_exercise_id = Some(exercise_id);
404412
updated.current_exercise_start = Some(exercise_start);
413+
updated.current_exercise_from_pending = true;
405414
save_session(updated);
406415
}
407416
/// Append `exercise` to the custom-exercises signal and persist it to the backend.

src/services/storage.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,6 +1698,7 @@ mod tests {
16981698
paused_at: None,
16991699
total_paused_duration: 0,
17001700
notes: String::new(),
1701+
current_exercise_from_pending: false,
17011702
};
17021703
native_storage::put_item(native_storage::STORE_SESSIONS, &session.id, &session).unwrap();
17031704
let loaded: Vec<WorkoutSession> =
@@ -1724,6 +1725,7 @@ mod tests {
17241725
paused_at: None,
17251726
total_paused_duration: 0,
17261727
notes: String::new(),
1728+
current_exercise_from_pending: false,
17271729
};
17281730
let s2 = WorkoutSession {
17291731
id: id.into(),
@@ -1737,6 +1739,7 @@ mod tests {
17371739
paused_at: None,
17381740
total_paused_duration: 0,
17391741
notes: String::new(),
1742+
current_exercise_from_pending: false,
17401743
};
17411744
native_storage::put_item(native_storage::STORE_SESSIONS, id, &s1).unwrap();
17421745
native_storage::put_item(native_storage::STORE_SESSIONS, id, &s2).unwrap();
@@ -1770,6 +1773,7 @@ mod tests {
17701773
paused_at: None,
17711774
total_paused_duration: 0,
17721775
notes: String::new(),
1776+
current_exercise_from_pending: false,
17731777
};
17741778
native_storage::put_item(native_storage::STORE_SESSIONS, id, &session).unwrap();
17751779
native_storage::delete_item(native_storage::STORE_SESSIONS, id).unwrap();
@@ -1948,6 +1952,7 @@ mod tests {
19481952
paused_at: None,
19491953
total_paused_duration: 0,
19501954
notes: String::new(),
1955+
current_exercise_from_pending: false,
19511956
};
19521957
native_storage::put_item(native_storage::STORE_SESSIONS, &session.id, &session).unwrap();
19531958
let loaded: Vec<WorkoutSession> =
@@ -2002,6 +2007,7 @@ mod tests {
20022007
paused_at: None,
20032008
total_paused_duration: 0,
20042009
notes: String::new(),
2010+
current_exercise_from_pending: false,
20052011
};
20062012
let done = WorkoutSession {
20072013
id: "paged_done".into(),
@@ -2015,6 +2021,7 @@ mod tests {
20152021
paused_at: None,
20162022
total_paused_duration: 0,
20172023
notes: String::new(),
2024+
current_exercise_from_pending: false,
20182025
};
20192026
native_storage::put_item(native_storage::STORE_SESSIONS, &active.id, &active).unwrap();
20202027
native_storage::put_item(native_storage::STORE_SESSIONS, &done.id, &done).unwrap();
@@ -2047,6 +2054,7 @@ mod tests {
20472054
paused_at: None,
20482055
total_paused_duration: 0,
20492056
notes: String::new(),
2057+
current_exercise_from_pending: false,
20502058
};
20512059
native_storage::put_item(native_storage::STORE_SESSIONS, &s.id, &s).unwrap();
20522060
}
@@ -2094,6 +2102,7 @@ mod tests {
20942102
paused_at: None,
20952103
total_paused_duration: 0,
20962104
notes: String::new(),
2105+
current_exercise_from_pending: false,
20972106
}
20982107
}
20992108
fn make_exercise_log(exercise_id: &str, start: u64, end: Option<u64>) -> ExerciseLog {
@@ -2126,6 +2135,7 @@ mod tests {
21262135
paused_at: None,
21272136
total_paused_duration: 0,
21282137
notes: String::new(),
2138+
current_exercise_from_pending: false,
21292139
};
21302140
let done = WorkoutSession {
21312141
id: id_done.into(),
@@ -2139,6 +2149,7 @@ mod tests {
21392149
paused_at: None,
21402150
total_paused_duration: 0,
21412151
notes: String::new(),
2152+
current_exercise_from_pending: false,
21422153
};
21432154
native_storage::put_item(native_storage::STORE_SESSIONS, id_active, &active).unwrap();
21442155
native_storage::put_item(native_storage::STORE_SESSIONS, id_done, &done).unwrap();
@@ -2192,6 +2203,7 @@ mod tests {
21922203
paused_at: None,
21932204
total_paused_duration: 0,
21942205
notes: String::new(),
2206+
current_exercise_from_pending: false,
21952207
};
21962208
native_storage::put_item(native_storage::STORE_SESSIONS, id, &session).unwrap();
21972209
let rows = native_storage::compute_bests_rows().expect("compute_bests_rows failed");

0 commit comments

Comments
 (0)