diff --git a/app/routers/pages.py b/app/routers/pages.py index 3d1ded6..14fceb1 100644 --- a/app/routers/pages.py +++ b/app/routers/pages.py @@ -51,6 +51,7 @@ def _today_context(db: Session, user_id: int, **extra) -> dict: return { "build_items": build_items, "quit_items": quit_items, + "yesterday_missed": log_service.get_yesterday_missed_items(db, user_id), "total_count": len(all_items), "checked_count": sum(1 for item in all_items if item.checked), **extra, @@ -89,6 +90,26 @@ def toggle_today_page(request: Request, habit_id: int, db: Session = Depends(get ) +@router.post("/today/{habit_id}/toggle-yesterday") +def toggle_yesterday_page(request: Request, habit_id: int, db: Session = Depends(get_db)): + current = _current_user_or_redirect(request, db) + if isinstance(current, RedirectResponse): + return current + + habit = habit_service.get_habit(db, habit_id, current.id) + if habit is None: + return Response(status_code=404) + + yesterday = date.today() - timedelta(days=1) + _, milestone_streak = log_service.toggle_check_and_celebrate(db, habit, yesterday) + extra = {"celebrate_habit_name": habit.name, "celebrate_streak": milestone_streak} if milestone_streak else {} + return templates.TemplateResponse( + request, + "partials/today_content.html", + {"logged_in": True, "current_user": current, **_today_context(db, current.id, **extra)}, + ) + + @router.get("/habits") def habits_page(request: Request, tab: str = "build", db: Session = Depends(get_db)): current = _current_user_or_redirect(request, db) diff --git a/app/services/log_service.py b/app/services/log_service.py index 226f21a..d4c9c4f 100644 --- a/app/services/log_service.py +++ b/app/services/log_service.py @@ -50,6 +50,13 @@ def get_today_items(db: Session, user_id: int, target_date: date) -> tuple[list[ return build_items, quit_items +def get_yesterday_missed_items(db: Session, user_id: int) -> list[TodayItem]: + """어제 예정되어 있었지만 아직 체크하지 않은 습관 목록 (형성+중단 합쳐서, /today 화면의 소급 체크용).""" + yesterday = date.today() - timedelta(days=1) + build_items, quit_items = get_today_items(db, user_id, yesterday) + return [item for item in build_items + quit_items if not item.checked] + + def toggle_check(db: Session, habit_id: int, log_date: date) -> bool: """체크 상태를 반전시키고 토글 후의 체크 여부를 반환한다. diff --git a/app/templates/partials/today_content.html b/app/templates/partials/today_content.html index 0f9c394..8cb9dab 100644 --- a/app/templates/partials/today_content.html +++ b/app/templates/partials/today_content.html @@ -24,6 +24,15 @@ +{% if yesterday_missed %} +

어제 놓친 습관

+
+ {% for item in yesterday_missed %} + {% include "partials/yesterday_item.html" %} + {% endfor %} +
+{% endif %} +

형성 습관

{% for item in build_items %} diff --git a/app/templates/partials/yesterday_item.html b/app/templates/partials/yesterday_item.html new file mode 100644 index 0000000..8a576ee --- /dev/null +++ b/app/templates/partials/yesterday_item.html @@ -0,0 +1,16 @@ +
+
+ +
+
{{ item.name }}
+ {% if item.condition_text %}
{{ item.condition_text }}
{% endif %} +
+
+
diff --git a/tests/test_log_service.py b/tests/test_log_service.py index ef235ee..0153888 100644 --- a/tests/test_log_service.py +++ b/tests/test_log_service.py @@ -123,6 +123,33 @@ def test_get_today_items_excludes_habits_not_scheduled_today(db_session, test_us assert quit_items == [] +# ---- get_yesterday_missed_items ---- + + +def test_get_yesterday_missed_items_includes_unchecked_scheduled_habit(db_session, test_user): + habit = _make_habit(db_session, test_user.id, name="어제 놓친 습관") + + missed = log_service.get_yesterday_missed_items(db_session, test_user.id) + assert [item.habit_id for item in missed] == [habit.id] + + +def test_get_yesterday_missed_items_excludes_already_checked(db_session, test_user): + habit = _make_habit(db_session, test_user.id) + _check(db_session, habit.id, date.today() - timedelta(days=1)) + + missed = log_service.get_yesterday_missed_items(db_session, test_user.id) + assert missed == [] + + +def test_get_yesterday_missed_items_excludes_habits_not_scheduled_yesterday(db_session, test_user): + yesterday = date.today() - timedelta(days=1) + other_day_mask = 1 << ((yesterday.weekday() + 1) % 7) # 어제가 아닌 요일 하나만 선택 + _make_habit(db_session, test_user.id, weekdays_mask=other_day_mask) + + missed = log_service.get_yesterday_missed_items(db_session, test_user.id) + assert missed == [] + + # ---- get_habit_stats: completion_rate ---- diff --git a/tests/test_pages_today.py b/tests/test_pages_today.py index d97315b..1e20a16 100644 --- a/tests/test_pages_today.py +++ b/tests/test_pages_today.py @@ -1,4 +1,7 @@ +from datetime import date, timedelta + from app.models.habit import ALL_WEEKDAYS_MASK, HabitType +from app.models.habit_log import HabitLog from app.schemas.habit import HabitCreate from app.services import habit_service @@ -38,3 +41,31 @@ def test_toggle_today_page_for_other_users_habit_returns_404(auth_client, db_ses response = auth_client.post(f"/today/{others_habit.id}/toggle") assert response.status_code == 404 + + +def test_today_page_shows_yesterday_missed_habit(auth_client, db_session, test_user): + _make_habit(db_session, test_user.id, name="어제 놓친 습관") + + response = auth_client.get("/today") + assert response.status_code == 200 + assert "어제 놓친 습관" in response.text + + +def test_toggle_yesterday_page_creates_log_for_yesterday(auth_client, db_session, test_user): + habit = _make_habit(db_session, test_user.id, name="어제 습관") + + response = auth_client.post(f"/today/{habit.id}/toggle-yesterday") + assert response.status_code == 200 + + yesterday = date.today() - timedelta(days=1) + log = db_session.query(HabitLog).filter_by(habit_id=habit.id, log_date=yesterday).one() + assert log is not None + # 체크되었으니 더 이상 "어제 놓친 습관" 목록에 나오지 않아야 한다. + assert "어제 놓친 습관" not in response.text + + +def test_toggle_yesterday_page_for_other_users_habit_returns_404(auth_client, db_session, other_user): + others_habit = _make_habit(db_session, other_user.id, name="남의 습관") + + response = auth_client.post(f"/today/{others_habit.id}/toggle-yesterday") + assert response.status_code == 404