Skip to content

Commit 4674219

Browse files
delete and save button + report update
1 parent dd27ae2 commit 4674219

3 files changed

Lines changed: 76 additions & 15 deletions

File tree

my_studysmart.xlsx

4.87 KB
Binary file not shown.

src/study_smart/app.py

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
dbc.Row([
4444
dbc.Col(dbc.Button("💾 Save", id="save-button", color="secondary", size="sm")),
4545
dbc.Col(dbc.Button("📂 Load", id="load-button", color="secondary", size="sm")),
46+
dbc.Col(html.Div(id="save-status"), width="auto"),
4647
], className="mb-3"),
4748
html.Div(id="exam-table")
4849
], width=6)
@@ -329,12 +330,17 @@ def add_exam(n_clicks, name, exam_date, hours, topics):
329330
})
330331

331332
rows = []
332-
for e in exams:
333+
for i, e in enumerate(exams):
333334
rows.append(html.Tr([
334335
html.Td(e["name"]),
335336
html.Td(e["date"]),
336337
html.Td(f"{e['hours']}hrs"),
337-
html.Td(", ".join(e["topics"]) if e["topics"] else "—")
338+
html.Td(", ".join(e["topics"]) if e["topics"] else "—"),
339+
html.Td(dbc.Button("🗑️",
340+
id={"type": "delete-exam", "index": i},
341+
color="danger",
342+
size="sm"
343+
))
338344
]))
339345

340346
table = dbc.Table([
@@ -347,21 +353,56 @@ def add_exam(n_clicks, name, exam_date, hours, topics):
347353

348354
return table
349355

356+
@app.callback(
357+
Output("exam-table", "children", allow_duplicate=True),
358+
Input({"type": "delete-exam", "index": ALL}, "n_clicks"),
359+
prevent_initial_call=True
360+
)
361+
def delete_exam(n_clicks_list):
362+
if not any(n_clicks_list):
363+
return dash.no_update
364+
365+
triggered_index = ctx.triggered_id["index"]
366+
exams.pop(triggered_index)
367+
368+
rows = []
369+
for i, e in enumerate(exams):
370+
rows.append(html.Tr([
371+
html.Td(e["name"]),
372+
html.Td(e["date"]),
373+
html.Td(f"{e['hours']}hrs"),
374+
html.Td(", ".join(e["topics"]) if e["topics"] else "—"),
375+
html.Td(dbc.Button("🗑️",
376+
id={"type": "delete-exam", "index": i},
377+
color="danger",
378+
size="sm"
379+
))
380+
]))
381+
382+
table = dbc.Table([
383+
html.Thead(html.Tr([
384+
html.Th("Exam"), html.Th("Date"),
385+
html.Th("Hours"), html.Th("Topics"), html.Th("")
386+
])),
387+
html.Tbody(rows)
388+
], bordered=True, hover=True, striped=True, size="sm")
389+
390+
return table if exams else html.P("No exams added.")
350391

351392
# Callback — save exams
352393
@app.callback(
353394
Output("save-button", "children"),
395+
Output("save-status", "children"),
354396
Input("save-button", "n_clicks"),
355397
prevent_initial_call=True
356398
)
357399
def save_exams(n_clicks):
358400
if not exams:
359-
return "💾 Save"
401+
return "💾 Save", ""
360402
df = pd.DataFrame(exams)
361403
df["topics"] = df["topics"].apply(lambda t: ", ".join(t) if isinstance(t, list) else "")
362404
df.to_excel("my_studysmart.xlsx", index=False)
363-
return "✅ Saved!"
364-
405+
return "💾 Save", "✅ Saved!"
365406

366407
# Callback — load exams
367408
@app.callback(
@@ -389,18 +430,23 @@ def load_exams(n_clicks):
389430
})
390431

391432
rows = []
392-
for e in exams:
433+
for i, e in enumerate(exams):
393434
rows.append(html.Tr([
394435
html.Td(e["name"]),
395436
html.Td(e["date"]),
396437
html.Td(f"{e['hours']}hrs"),
397-
html.Td(", ".join(e["topics"]) if e["topics"] else "—")
398-
]))
438+
html.Td(", ".join(e["topics"]) if e["topics"] else "—"),
439+
html.Td(dbc.Button("🗑️",
440+
id={"type": "delete-exam", "index": i},
441+
color="danger",
442+
size="sm"
443+
))
444+
]))
399445

400446
table = dbc.Table([
401447
html.Thead(html.Tr([
402448
html.Th("Exam"), html.Th("Date"),
403-
html.Th("Hours"), html.Th("Topics")
449+
html.Th("Hours"), html.Th("Topics"), html.Th("")
404450
])),
405451
html.Tbody(rows)
406452
], bordered=True, hover=True, striped=True, size="sm")
@@ -629,5 +675,3 @@ def generate_schedule(n_clicks, spaced_repetition, default_hours, start_date):
629675
if __name__ == "__main__":
630676
app.run(debug=True)
631677

632-
633-

tests/test_app.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- build_commitment_list: commitment display
1010
- build_tips: study tip generation
1111
- add_exam callback: validation and global state
12+
- delete_exam callback: removal of exams
1213
- add_commitment callback: accumulation and validation
1314
- delete_commitment callback: removal and recompute
1415
- save_exams / load_exams: Excel persistence
@@ -29,6 +30,7 @@
2930
build_tips,
3031
build_commitment_list,
3132
add_exam,
33+
delete_exam,
3234
add_commitment,
3335
delete_commitment,
3436
save_exams,
@@ -323,6 +325,19 @@ def test_add_exam_multiple_exams_all_appear_in_table():
323325
assert isinstance(result, dbc.Table)
324326
assert len(app.exams) == 2
325327

328+
# Test for delete_exam callback
329+
330+
def test_delete_exam_removes_correct_item():
331+
"""Test that deleting index 0 removes the first exam."""
332+
app.exams.append({"name": "Stats", "date": "2026-06-01", "hours": 10, "topics": []})
333+
app.exams.append({"name": "Math", "date": "2026-06-05", "hours": 8, "topics": []})
334+
335+
with patch("study_smart.app.ctx") as mock_ctx:
336+
mock_ctx.triggered_id = {"index": 0}
337+
delete_exam([1, None])
338+
339+
assert len(app.exams) == 1
340+
assert app.exams[0]["name"] == "Math"
326341

327342

328343
# Tests for add_commitment callback
@@ -451,16 +466,18 @@ def test_build_weekly_view_no_rev_label_when_not_spaced():
451466

452467
def test_save_exams_empty_returns_save_label():
453468
"""Test that saving with no exams returns the original '💾 Save' label."""
454-
result = save_exams(1)
455-
assert result == "💾 Save"
469+
button, status = save_exams(1)
470+
assert button == "💾 Save"
471+
assert status == ""
456472

457473

458474
def test_save_exams_with_exams_returns_saved_label():
459475
"""Test that saving exams returns '✅ Saved!' label."""
460476
app.exams.append({"name": "Stats", "date": "2026-06-01", "hours": 10, "topics": ["Ch1"]})
461477
with patch.object(pd.DataFrame, "to_excel"):
462-
result = save_exams(1)
463-
assert result == "✅ Saved!"
478+
button, status = save_exams(1)
479+
assert button == "💾 Save"
480+
assert status == "✅ Saved!"
464481

465482

466483
def test_save_exams_serialises_topics_as_string():

0 commit comments

Comments
 (0)