Skip to content

[Feat-T3-178] 루틴 완료 디자인 반영 및 서버 v2 연동#56

Merged
choijungp merged 3 commits intodevelopfrom
feat/routine-completion
Aug 22, 2025
Merged

[Feat-T3-178] 루틴 완료 디자인 반영 및 서버 v2 연동#56
choijungp merged 3 commits intodevelopfrom
feat/routine-completion

Conversation

@choijungp
Copy link
Copy Markdown
Contributor

@choijungp choijungp commented Aug 22, 2025

🌁 Background

루틴 완료 구현했습니다 ~

📱 Screenshot

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-08-23.at.02.54.51.mp4

👩‍💻 Contents

  • 루틴 완료 API v2 연동
  • RoutineView 수정
  • 해당 날짜에 루틴을 모두 수행했다면 별표 표시 뜨도록 ..

📝 Review Note

죽어가는 뇌로 구현했기 때문에 .. 이상하네 ? 왜 이르케 구현했지 ..
라고 생각이 드실수도 있어요 .........

근데 일단 굴러감 ㅠㅠ
진짜 추후 빡세게 리팩해부아요 🥺

📣 Related Issue

  • close #T3-178

Summary by CodeRabbit

  • 신기능

    • 주간 캘린더에 “전체 완료” 아이콘 표시로 하루의 모든 루틴 완료 여부를 한눈에 확인.
    • 루틴 카드에서 메인/서브 루틴을 개별 탭으로 바로 완료 처리 가능.
    • 선택한 날짜의 데일리 루틴 불러오기 및 새로고침 동작 추가.
  • 개선

    • “00:00” 시간대 루틴을 “하루 종일”로 표시하여 가독성 향상.
    • 루틴/시간 라벨 다중 줄 표시로 긴 제목도 깔끔하게 표시.
    • 루틴 업데이트 및 데이터 동기화 안정성 향상.
  • UI 변경

    • 루틴 삭제 확인 흐름을 정비하여 안내 및 조작성을 개선.

@choijungp choijungp requested a review from taipaise August 22, 2025 17:58
@choijungp choijungp self-assigned this Aug 22, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 22, 2025

Walkthrough

루틴 완료 데이터 모델과 엔드포인트를 갱신하고, 저장소/도메인 프로토콜 시그니처를 RoutineEntity 기준으로 일괄 변경했습니다. 홈 화면은 VC로 전환되고, 주차/일간 루틴 조회와 “모두 완료” 상태 전파, 메인/서브 루틴 토글 델리게이트, 신규 SubRoutineView 도입, 삭제 알럿 뷰 제거가 포함됩니다.

Changes

Cohort / File(s) Summary
DTO 리모델링
Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift
RoutineCompletionListDTO에서 performedDate 제거. RoutineCompletionDTO 필드 교체: completeYn/historySeq/routineTyperoutineCompleteYn/subRoutineCompleteYn.
엔드포인트 메서드 변경
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift
updateRoutineCompletion HTTP 메서드 POST → PUT 분리 지정. 경로는 동일(/completions).
저장소 구현 및 매핑 갱신
Projects/DataSource/Sources/Repository/RoutineRepository.swift
updateRoutineCompletions 인자 타입 [\RoutineCompletionEntity`][`RoutineEntity`]. DTO 매핑을 새 필드로 변경, performedDate` 관련 로직 제거.
도메인 엔티티/프로토콜 정리
Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift, .../Protocol/Repository/RoutineRepositoryProtocol.swift, .../Protocol/UseCase/RoutineUseCaseProtocol.swift, .../UseCase/Routine/RoutineUseCase.swift
RoutineCompletionEntity 파일 삭제. 저장소/유즈케이스 프로토콜 및 구현 메서드 시그니처를 [\RoutineEntity`]`로 변경.
프레젠테이션 모델 매핑 추가
Projects/Presentation/Sources/Home/Model/Routine.swift
Routine.toRoutineEntity() 추가: 뷰 모델 → RoutineEntity 매핑 메서드 신설.
홈 공용/네비게이션
Projects/Presentation/Sources/Common/View/TabBarView.swift
홈 탭 루트 뷰 HomeViewHomeViewController로 교체.
홈 달력 컴포넌트 개선
Projects/Presentation/Sources/Home/View/Component/DateView.swift, .../WeekView.swift
DateView에 전체 완료 아이콘 추가 및 updateAllCompleted() 제공. 콜백명 didTappedDateButtondidTapDateButton. WeekView는 주간 전체 완료 날짜 집합 관리 및 갱신 API 추가.
루틴 아이템/서브루틴 UI 도입
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift, .../SubRoutineView.swift
RoutineViewDelegate 도입, 메인/서브 루틴 토글 처리. 스택 기반 레이아웃, 서브루틴별 SubRoutineView 신설 및 델리게이트 연결.
홈 컨트롤러 리팩터링
Projects/Presentation/Sources/Home/View/HomeViewController.swift
타입명 HomeViewHomeViewController. RoutineViewDelegate/WeekViewDelegate/FloatingMenuViewDelegate 확장 이동. 일간 조회 및 “모두 완료” 상태 바인딩 추가.
컴포넌트 제거
Projects/Presentation/Sources/Home/View/Component/RoutineDeleteAlertView.swift
삭제 알럿 뷰 및 델리게이트 완전 제거.

Sequence Diagram(s)

sequenceDiagram
    participant U as 사용자
    participant H as HomeViewController
    participant VM as HomeViewModel
    participant UC as RoutineUseCase
    participant RP as RoutineRepository
    participant API as RoutineEndpoint(Server)

    rect rgb(245,248,255)
    note over H,VM: 주간 기준 일간 루틴 조회
    U->>H: 날짜 선택/주 이동
    H->>VM: Input.fetchDailyRoutine
    VM->>UC: fetchRoutines(startDate..endDate)
    UC->>RP: fetchRoutines(...)
    RP->>API: GET /routines?startDate&endDate
    API-->>RP: 루틴 목록
    RP-->>UC: 루틴 목록
    UC-->>VM: 루틴 목록
    VM-->>H: Output.dailyRoutinesPublisher
    VM-->>H: Output.allCompletedRoutineDatePublisher
    H->>WeekView: updateAllCompletedState([Date])
    end
Loading
sequenceDiagram
    participant U as 사용자
    participant H as HomeViewController
    participant RV as RoutineView
    participant VM as HomeViewModel
    participant UC as RoutineUseCase
    participant RP as RoutineRepository
    participant API as RoutineEndpoint(Server)

    rect rgb(245,255,245)
    note over RV,H: 메인/서브 루틴 완료 토글
    U->>RV: 체크 토글(메인/서브)
    RV->>H: RoutineViewDelegate.didTap...(Routine)
    H->>VM: updateRoutineCompletion(updatedRoutine)
    VM->>UC: updateRoutineCompletions([RoutineEntity])
    UC->>RP: updateRoutineCompletions([RoutineEntity])
    RP->>API: PUT /completions (routineCompleteYn, subRoutineCompleteYn[])
    API-->>RP: 200 OK
    RP-->>UC: 완료
    UC-->>VM: 완료
    VM-->>H: Output.updateRoutineCompletionPublisher(성공)
    H->>WeekView: updateAllCompletedState([Date]) (갱신 반영)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • taipaise

Poem

깡총, 깡총—체크표시 반짝! ✅
메인도 서브도 딱딱—PUT으로 착착.
날짜엔 별 하나, 모두 완료의 약속 ✨
홈은 컨트롤러로, 길도 반듯하게.
오늘도 루틴 토끼는 리스트를 정리해—
완료의 당근은 나의 것! 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/routine-completion

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or @coderabbitai 요약 to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Projects/Presentation/Sources/Home/View/HomeViewController.swift (1)

468-471: UIStackView에서 arrangedSubviews 제거 방식 수정 필요

removeFromSuperview()만 호출하면 UIStackView의 arrangedSubviews 배열에는 남아 레이아웃/제약 경고 및 메모리 보유가 발생할 수 있습니다. removeArrangedSubview(_:)와 병행 호출이 필요합니다.

-        routineStackView.arrangedSubviews.forEach {
-            $0.removeFromSuperview()
-        }
+        routineStackView.arrangedSubviews.forEach { v in
+            routineStackView.removeArrangedSubview(v)
+            v.removeFromSuperview()
+        }
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)

204-241: 비동기 경쟁 조건: 캐시 미스 시 ‘즉시 빈 목록 발행’로 인한 깜빡임/오동작 가능

fetchRoutines(startDate:endDate:)는 내부에서 Task로 비동기 조회를 시작합니다. 직후에 if let dailyRoutines ... else { routinesSubject.send([]) }가 실행되어, 네트워크 응답이 오기 전 빈 목록이 발행될 수 있습니다(플리커링, 잘못된 빈 상태 노출).

최소 수정안: 네트워크 조회를 시작한 뒤 조기 반환하고, 실데이터 발행은 조회 완료 시점(VC에서 성공 수신 후 .fetchDailyRoutine 재호출 또는 본 VM 내부 로직)으로 일임합니다.

         fetchRoutines(startDate: startDate, endDate: endDate)
-
-        if let dailyRoutines = routines[date.convertToString(dateType: .yearMonthDate)] {
-            // 새로 캐싱된 루틴들에서 데이터가 있을 때,
-            routinesSubject.send(dailyRoutines)
-        } else {
-            // 새로 캐싱된 루틴들에서 데이터가 없을 때,
-            routinesSubject.send([])
-        }
+        // 비동기 조회 완료 후(별도 파이프라인) 발행
+        return

추가로, 중첩 Task 사용을 줄이고 fetchRoutines(startDate:endDate:)async로 변경한 뒤 await로 순서를 보장하는 구조로의 리팩터링을 권장합니다.

🧹 Nitpick comments (17)
Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)

72-74: 접근성: 장식용 아이콘은 보조기기에서 숨기세요

완료 여부를 시각적으로만 전달하는 장식 요소라면 VoiceOver 노이즈를 줄이기 위해 접근성에서 제외하는 것이 좋습니다.

적용 diff:

         allCompletedIcon.image = BitnagilIcon.asteriskIcon
         allCompletedIcon.isHidden = true
+        allCompletedIcon.isAccessibilityElement = false

Also applies to: 99-102

Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift (1)

14-16: DTO 필드 계약 고정: CodingKeys 명시로 리스크 최소화

서버 스펙이 정확히 이 키를 요구한다면 명시적인 CodingKeys로 계약을 고정해 두는 편이 안전합니다(리네이밍으로 인한 회귀 방지).

적용 예:

 struct RoutineCompletionDTO: Encodable {
     let routineId: String
     let routineCompleteYn: Bool
     let subRoutineCompleteYn: [Bool]
+    private enum CodingKeys: String, CodingKey {
+        case routineId, routineCompleteYn, subRoutineCompleteYn
+    }
 }
  • subRoutineCompleteYn의 길이가 서버가 인지하는 서브루틴 개수와 항상 일치하는지(패딩/누락 없음) 한 번 더 검증 부탁드립니다.
Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift (2)

84-87: updateSubRoutineState는 외부 노출 불필요 — 접근 제한자 축소

외부에서 호출될 이유가 없어 보입니다. private으로 감추면 인터페이스가 더 명확해집니다.

적용 diff:

-    func updateSubRoutineState() {
+    private func updateSubRoutineState() {
         let subRoutineCheckIcon = subRoutine.isDone ? BitnagilIcon.checkedCircleSmallIcon : BitnagilIcon.uncheckedCircleSmallIcon
         subRoutineCheckButton.setImage(subRoutineCheckIcon, for: .normal)
     }

25-29: 튜플 기반 상태 대신 명시적 타입 도입 고려

(title, isDone) 튜플은 의미 전달이 약합니다. 작은 불변/가변 필드를 갖는 struct로 치환하면 확장성(메모, 식별자 등)과 가독성이 좋아집니다.

예시:

struct SubRoutineItem {
    let title: String
    var isDone: Bool
}

// 사용
private var subRoutine: SubRoutineItem { didSet { updateSubRoutineState() } }
Projects/Presentation/Sources/Home/Model/Routine.swift (1)

23-37: 양방향 매핑 전반적으로 타당 · 서브루틴 길이 불일치 가드 추가 권장

프로퍼티 매핑이 DTO ↔ Domain ↔ Presentation 간 대칭적으로 보입니다. 다만 subRoutinessubRoutineCompleted의 길이가 달라질 경우 서버와 의미 불일치가 발생합니다. 디버그에서라도 불일치를 조기에 감지하도록 assert를 추가하는 것이 안전합니다.

아래처럼 간단히 가드를 넣어 주세요.

 func toRoutineEntity() -> RoutineEntity {
-        return RoutineEntity(
+        assert(subRoutines.count == subRoutineCompleted.count, "subRoutines.count와 subRoutineCompleted.count가 다릅니다.")
+        return RoutineEntity(
             routineId: id,
             routineName: title,
             repeatDay: repeatDay.map({ $0.rawValue }),
             executionTime: startTime.convertToString(dateType: .time),
             routineCompleteYn: isDone,
             subRoutineNames: subRoutines,
             subRoutineCompleteYn: subRoutineCompleted,
             recommendedRoutineType: routineType?.rawValue,
             routineDeletedYn: isDeleted,
             routineStartDate: startDate.convertToString(dateType: .yearMonthDate),
             routineEndDate: endDate.convertToString(dateType: .yearMonthDate))
 }

소소한 개선:

  • repeatDay.map({ $0.rawValue })repeatDay.map(\.rawValue)로 간결화 가능.
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)

77-83: 테스트 용이성과 격리성 향상을 위한 의존성 주입 고려

NetworkService.shared 싱글톤을 직접 참조하면 단위 테스트가 어려워집니다. 생성자 주입으로 NetworkServiceProtocol을 받아 사용하도록 변경하면 목킹이 쉬워집니다.

Projects/Presentation/Sources/Home/View/HomeViewController.swift (4)

409-416: 로딩 인디케이터 hide 타이밍 정합성

fetchRoutineResultPublisher에서 hideIndicatorView()를 호출하고, 이후 .fetchDailyRoutine가 다시 네트워크를 트리거하면 스피너가 너무 일찍 사라질 수 있습니다. 아래 중 한 가지로 정리하는 것을 권장합니다.

  • fetch 완료 → daily fetch 시작 전까지 스피너 유지, daily 결과 수신 시 hide
  • 혹은 daily fetch는 캐시만 접근하도록 보장하고 스피너는 여기서 hide

434-441: 루틴 완료 후 재조회 흐름은 합리적이나, UX 보완 제안(탭 중 연속 입력 방지)

업데이트 성공 시 .refreshDailyRoutine로 재조회하는 흐름은 좋습니다. 다만 업데이트 동안 중복 탭을 방지하고 피드백을 주기 위해, 탭 시점에 스피너를 보여주는 것이 좋습니다. 아래처럼 델리게이트 메서드에서 showIndicatorView()를 호출해 주세요.

 // 아래 변경은 RoutineViewDelegate 구현부(603~622)에서 수행
-        viewModel.action(input: .updateRoutineCompletion(updatedRoutine: updatedRoutine))
+        self?.showIndicatorView()
+        viewModel.action(input: .updateRoutineCompletion(updatedRoutine: updatedRoutine))

602-612: 메인 루틴 토글 시 서브루틴 UI 즉시 반영을 보장해 주세요

현재 메인 완료 토글 시 모델만 변경되어 서버 반영 후 리프레시까지 서브루틴 UI가 이전 상태로 보일 수 있습니다. RoutineView에서 서브루틴 뷰의 완료 상태를 즉시 동기화하도록 보완하는 것을 권장합니다(아래 RoutineView 코멘트 참고). 또한 탭 시 스피너 표시(위 코멘트) 적용을 권장합니다.


194-199: 플로팅 버튼 토글 시 숨김 처리 타이밍 개선으로 페이드 아웃 애니메이션 살리기

현재 toggleFloatingButton() 내부에서 isHidden을 즉시 토글한 뒤 alpha를 애니메이션하고 있어, 숨김 시 페이드 아웃이 보이지 않습니다(isHidden = true이면 그려지지 않음). 숨김 전에는 isHidden = false로 두고, 애니메이션 완료 콜백에서 숨김을 반영하세요.

아래는 toggleFloatingButton()(라인 555-566) 수정 예시입니다.

 private func toggleFloatingButton() {
     floatingButton.toggle()
     isShowingFloatingMenu.toggle()

-    floatingMenu.isHidden = !isShowingFloatingMenu
-    dimmedView.isHidden = !isShowingFloatingMenu
-
-    UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut]) {
-        self.dimmedView.alpha = self.isShowingFloatingMenu ? 1 : 0
-        self.floatingMenu.alpha = self.isShowingFloatingMenu ? 1 : 0
-    }
+    // 애니메이션 전에는 항상 표시 상태로 두고 alpha로 제어
+    floatingMenu.isHidden = false
+    dimmedView.isHidden = false
+    UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut]) {
+        self.dimmedView.alpha = self.isShowingFloatingMenu ? 1 : 0
+        self.floatingMenu.alpha = self.isShowingFloatingMenu ? 1 : 0
+    } completion: { _ in
+        // 완료 후 숨김 반영
+        self.floatingMenu.isHidden = !self.isShowingFloatingMenu
+        self.dimmedView.isHidden = !self.isShowingFloatingMenu
+    }
 }
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (4)

19-29: timeLabel 폭 상수(42pt) 재고 권장

"하루 종일" 같은 문구가 2줄로 접히며 컴팩트 폭에서는 가독성이 떨어질 수 있습니다. 동적 폭(예: 콘텐츠 Hugging/Compression 조정) 또는 약간 넓은 상수(예: 56~64pt)로 재검토를 권장합니다.


79-88: 메인 완료 토글 동작 자체는 OK, 단 UI 동기화 보완 필요

토글 후 self.routine = updatedRoutinedidSet에서 메인 체크 아이콘이 갱신됩니다. 서브루틴의 체크 상태도 즉시 반영되도록 updateRoutineState()에서 서브루틴 뷰 상태까지 갱신해 주세요(아래 코멘트 참조).


157-160: updateRoutineState에서 서브루틴 상태도 동기화

메인 체크 아이콘만 갱신하고 있어, 모델의 subRoutineCompleted 변화가 서브뷰에 즉시 반영되지 않을 수 있습니다.

     func updateRoutineState() {
         let isDone = routine.isDone
         mainRoutineCheckButton.setImage(isDone ? BitnagilIcon.checkedCircleIcon : BitnagilIcon.uncheckedCircleIcon, for: .normal)
+        // 서브루틴 완료 상태 UI 동기화
+        for (index, view) in subRoutineViews {
+            guard index < routine.subRoutineCompleted.count else { continue }
+            let completed = routine.subRoutineCompleted[index]
+            view.setCompleted?(completed)
+        }
     }

SubRoutineView에 완료 상태를 반영하는 퍼블릭 API가 없다면 추가 구현이 필요합니다.


163-170: 델리게이트 호출 시 명시적 전달 및 서브뷰 UI 즉시 반영

  • self.routine = updatedRoutinedelegate?.routineView(self, didTapSubRoutine: routine)는 동작상 문제는 없지만, 가독성을 위해 updatedRoutine을 명시적으로 전달해 주세요.
  • 탭한 서브루틴 뷰의 UI는 SubRoutineView 내부에서 이미 토글될 수도 있으나, 일관성을 위해 본 뷰에서도 해당 인덱스의 상태를 즉시 동기화해 주는 것을 권장합니다.
-        self.routine = updatedRoutine
-        self.delegate?.routineView(self, didTapSubRoutine: routine)
+        self.routine = updatedRoutine
+        // UI 동기화(옵셔널)
+        subRoutineViews[index]?.setCompleted?(updatedRoutine.subRoutineCompleted[index])
+        self.delegate?.routineView(self, didTapSubRoutine: updatedRoutine)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (3)

137-143: 주 차 이동/주 시작일 계산 로직 OK, 단 캘린더 의존성은 명시화 권장

월요일 기준 산출이 명확합니다. 다만 Calendar.current의 지역 설정에 따라 주 시작 요일이 달라질 수 있어(예: 일부 지역은 일요일 시작), ISO8601 캘린더를 명시하거나 dateInterval(of:for:) 활용을 권장합니다.

예시:

var cal = Calendar(identifier: .iso8601)
cal.timeZone = .current
let weekStart = cal.date(from: cal.dateComponents([.yearForWeekOfYear, .weekOfYear], from: date)) ?? date

Also applies to: 145-151


243-251: 재조회 시 순서 보장

refreshSelectedDateRoutines()에서도 Task 내부에서 다시 fetchRoutines(...)가 또 다른 Task를 생성합니다. 순서/경쟁을 명확히 하려면 fetchRoutinesasync로 바꾸고 여기서 await 한 뒤 fetchDailyRoutine/fetchAllCompletedRoutine을 호출하는 것을 권장합니다.


257-263: 업데이트 에러 처리 TODO 정리

성공 시 true 발행만 있고, 실패 시 TODO가 남아 있습니다. 실패 시에도 updateRoutineCompletionResultSubject.send(false)를 발행하거나, 별도의 errorPublisher를 통해 UI에서 사용자 메시지를 노출할 수 있게 해 주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 63775d6 and 6f41e1b.

📒 Files selected for processing (16)
  • Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift (1 hunks)
  • Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (1 hunks)
  • Projects/DataSource/Sources/Repository/RoutineRepository.swift (1 hunks)
  • Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift (0 hunks)
  • Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (1 hunks)
  • Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1 hunks)
  • Projects/Presentation/Sources/Common/View/TabBarView.swift (1 hunks)
  • Projects/Presentation/Sources/Home/Model/Routine.swift (1 hunks)
  • Projects/Presentation/Sources/Home/View/Component/DateView.swift (4 hunks)
  • Projects/Presentation/Sources/Home/View/Component/RoutineDeleteAlertView.swift (0 hunks)
  • Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (3 hunks)
  • Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift (1 hunks)
  • Projects/Presentation/Sources/Home/View/Component/WeekView.swift (3 hunks)
  • Projects/Presentation/Sources/Home/View/HomeViewController.swift (8 hunks)
  • Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (8 hunks)
💤 Files with no reviewable changes (2)
  • Projects/Presentation/Sources/Home/View/Component/RoutineDeleteAlertView.swift
  • Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-16T09:21:15.038Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.

Applied to files:

  • Projects/Presentation/Sources/Common/View/TabBarView.swift
  • Projects/Presentation/Sources/Home/View/HomeViewController.swift
📚 Learning: 2025-08-01T06:38:18.687Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#31
File: Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift:97-103
Timestamp: 2025-08-01T06:38:18.687Z
Learning: choijungp의 HomeViewModel에서 fetchRoutines(for date: Date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 데이터 청크를 가져오기 위해 oldestDate와 latestDate를 모두 업데이트한다. 과거 날짜 요청 시에는 해당 날짜의 1주 전~1일 전, 미래 날짜 요청 시에는 해당 날짜의 1일 후~1주 후 범위로 설정한다.

Applied to files:

  • Projects/Presentation/Sources/Home/View/HomeViewController.swift
  • Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift
📚 Learning: 2025-07-16T09:09:13.869Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.

Applied to files:

  • Projects/Presentation/Sources/Home/View/Component/RoutineView.swift
🧬 Code graph analysis (11)
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (2)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
  • updateRoutineCompletions (77-86)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
  • updateRoutineCompletions (66-68)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
  • updateRoutineCompletions (77-86)
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (2)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
  • updateRoutineCompletions (77-86)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
  • updateRoutineCompletions (66-68)
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (3)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
  • createRoutine (13-28)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
  • createRoutine (50-52)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
  • updateRoutineCompletion (254-264)
Projects/Presentation/Sources/Home/Model/Routine.swift (2)
Projects/DataSource/Sources/DTO/RoutineDTO.swift (1)
  • toRoutineEntity (32-45)
Projects/Shared/Sources/Extension/Date+.swift (1)
  • convertToString (16-22)
Projects/Presentation/Sources/Home/View/Component/WeekView.swift (1)
Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)
  • updateAllCompleted (127-129)
Projects/Presentation/Sources/Home/View/HomeViewController.swift (3)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (4)
  • action (76-106)
  • fetchDailyRoutine (205-241)
  • selectDate (153-156)
  • updateRoutineCompletion (254-264)
Projects/Presentation/Sources/Home/View/Component/WeekView.swift (2)
  • updateAllCompletedState (108-115)
  • selectDate (118-122)
Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)
  • selectDate (119-121)
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (2)
Projects/Presentation/Sources/Home/View/HomeViewController.swift (2)
  • routineView (603-611)
  • routineView (613-622)
Projects/Shared/Sources/Extension/Date+.swift (1)
  • convertToString (16-22)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
  • updateRoutineCompletions (66-68)
Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift (1)
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (2)
  • subRoutineView (164-169)
  • configureLayout (95-155)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (4)
Projects/Shared/Sources/Extension/Date+.swift (1)
  • convertToString (16-22)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (2)
  • fetchRoutines (37-49)
  • updateRoutineCompletions (77-86)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (2)
  • fetchRoutines (23-29)
  • updateRoutineCompletions (66-68)
Projects/Presentation/Sources/Home/Model/Routine.swift (2)
  • toRoutine (41-54)
  • toRoutineEntity (24-37)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Home/View/Component/WeekView.swift

[Warning] 111-111: where clauses are preferred over a single if inside a for

(for_where)

Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift

[Warning] 199-199: TODOs should be resolved (에러 처리)

(todo)


[Warning] 261-261: TODOs should be resolved (에러 처리)

(todo)

🔇 Additional comments (15)
Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)

33-33: 콜백 이름 변경(didTapDateButton) – 모든 호출부 확인 완료

프로젝트 전체를 검색한 결과, 기존 식별자 didTappedDateButton 사용 흔적은 없고, 오직 새 이름인 didTapDateButton만 사용되고 있음을 확인했습니다.

  • DateView.swift: 선언부(var didTapDateButton) 및 호출부(didTapDateButton?(date))
  • WeekView.swift: 할당부(dateView.didTapDateButton = { … })

따라서 모든 외부 호출부가 올바르게 갱신되었으니 해당 리뷰 코멘트는 클로즈해도 무방합니다.

Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (1)

45-49: HTTP 메서드 분리(POST/PUT) 코드 상 확인 완료 — 서버 스펙(v2)과 응답 코드(200/204) 최종 확인 필요

  • Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift
    case .updateRoutineCompletion: → path "/completions", method .put 적용 확인
  • Projects/DataSource/Sources/Repository/RoutineRepository.swift (라인 85)
    networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self) 호출로 빈 바디 응답 처리 확인

위와 같이 POST/PUT 매핑 및 EmptyResponseDTO 사용이 코드상 적절히 적용되었습니다. 이제 API 문서 또는 서버 구현을 통해 해당 엔드포인트가 실제로 PUT을 요구하고 204 No Content(또는 빈 바디) 응답을 반환하는지 최종 검증 부탁드립니다.

Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift (1)

47-55: 클로저 캡처 안전 처리 LGTM

[weak self] 사용과 상태 동기화(모델 토글 → 뷰 갱신 → 델리게이트 통지) 플로우가 간결하고 명확합니다.

Projects/Presentation/Sources/Common/View/TabBarView.swift (1)

51-51: HomeViewController로 전환 LGTM

루트를 VC로 통일하는 방향이 네비게이션 합치에 유리합니다. 의존성 주입도 유지되어 있고 탭 구성과 잘 맞습니다.

Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (1)

21-21: RoutineCompletionEntity 참조 모두 제거됨을 확인했습니다
rg 검색 결과, 더 이상 RoutineCompletionEntity에 대한 참조가 없으며, updateRoutineCompletions(routines:) 호출부도 정상적으로 반영되어 있습니다. 해당 변경 사항을 승인합니다.

Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (1)

41-41: 검토 결과: Repository 시그니처 및 호출부 일치 확인 · 주석 보강 제안

  • updateRoutineCompletions(routines: [RoutineEntity]) 시그니처가 Protocol↔UseCase↔Repository 전반에 걸쳐 일치함을 확인했습니다.
  • 호출부에서는 모두 RoutineEntity만 전달하고 있으며, 이전 DTO/엔티티 (RoutineCompletionEntity) 사용 흔적이 없습니다.
  • Repository 구현 내부에서만 RoutineCompletionDTO를 매핑 용도로 사용 중임을 확인했습니다.

제안: Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift 파일에서 해당 메서드 선언 위에 아래와 같은 주석을 추가해 두면, 서버에 실제로 전달되는 필드를 명시하여 오용을 줄일 수 있습니다.

/// - Note: 서버에는 `routineId`, `routineCompleteYn`, `subRoutineCompleteYn` 필드만 전달됩니다.
func updateRoutineCompletions(routines: [RoutineEntity]) async throws

이상입니다.

Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)

66-68: 단순 위임 처리 LGTM

UseCase가 Repository로 안전하게 위임하며 예외 전파도 유지됩니다. 별도 로직이 없어 리스크 낮습니다.

Projects/Presentation/Sources/Home/View/HomeViewController.swift (4)

14-14: HomeView → HomeViewController 전환 LGTM

VC로의 전환과 BaseViewController 제네릭 파라미터 적용이 일관적입니다.


230-237: 레이아웃 계층 구성 적절

헤더/주차/리스트 영역을 StackView로 구성하고 컨테이너에 배치한 구조가 읽기 쉽습니다.

Also applies to: 234-236, 238-240, 241-243


458-463: 주 단위 전체완료 표시 바인딩 LGTM

WeekView.updateAllCompletedState로 상태를 전달하는 파이프라인이 명확합니다.


613-623: 서브루틴 토글 → 메인 완료 상태 동기화 로직은 타당

서브루틴 완료 개수로 isDone을 산출하는 로직이 명확합니다. 상위/하위 상태 간 일관성 보장 측면에서 적절합니다.

Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (1)

12-16: 델리게이트 디자인 합리적

메인/서브 루틴 탭을 분리한 델리게이트 시그니처가 명확합니다.

Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (3)

20-21: 일간 조회/리프레시 입력 추가 LGTM

입력 분리가 명확하여 흐름을 이해하기 쉽습니다.


33-33: 주간 ‘모두 완료’ 퍼블리셔 추가 및 내부 상태 맵 도입 LGTM

routinesCompleted 맵과 allCompletedRoutineDatePublisher 추가로 표시/상태 전파가 단순해졌습니다.

Also applies to: 38-38, 47-47, 72-74


184-202: 배치 조회 후 상태 맵/퍼블리셔 갱신 흐름 명확

DTO → Entity → Presentation 모델 변환 및 routinesCompleted/fetchRoutineResultSubject 갱신 순서가 적절합니다.

Comment on lines +77 to 83
func updateRoutineCompletions(routines: [RoutineEntity]) async throws {
let completionDTO = routines.map({ RoutineCompletionDTO(
routineId: $0.routineId,
completeYn: $0.completeYn,
historySeq: $0.historySeq,
routineType: $0.routineType) })

let completionListDTO = RoutineCompletionListDTO(
performedDate: performedDate,
routineCompletionInfos: completionDTO)
routineCompleteYn: $0.routineCompleteYn,
subRoutineCompleteYn: $0.subRoutineCompleteYn) })
let completionListDTO = RoutineCompletionListDTO(routineCompletionInfos: completionDTO)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

v2 DTO 필드 매핑/배열 정합성 검증 필요

  • 서버 스펙 상 routineCompleteYn, subRoutineCompleteYn 키명이 정확한지 재검증이 필요합니다(이전 스펙의 completeYn와 혼재 위험).
  • subRoutineCompleteYn는 서브루틴 개수와 정확히 동일한 길이여야 합니다. 불일치 시 서버에서 400/422가 날 수 있으니, 전송 전 길이 검증을 권장합니다.

아래처럼 전송 전에 길이 검증과 방어 로깅을 추가해 주세요.

 func updateRoutineCompletions(routines: [RoutineEntity]) async throws {
-    let completionDTO = routines.map({ RoutineCompletionDTO(
-        routineId: $0.routineId,
-        routineCompleteYn: $0.routineCompleteYn,
-        subRoutineCompleteYn: $0.subRoutineCompleteYn) })
+    // 방어: 서브루틴 이름/완료 여부 배열 길이 정합성 확인
+    try routines.forEach { r in
+        guard r.subRoutineNames.count == r.subRoutineCompleteYn.count else {
+            throw RoutineRepositoryError.invalidSubRoutineLength(
+                routineId: r.routineId,
+                names: r.subRoutineNames.count,
+                completes: r.subRoutineCompleteYn.count
+            )
+        }
+    }
+    let completionDTO = routines.map {
+        RoutineCompletionDTO(
+            routineId: $0.routineId,
+            routineCompleteYn: $0.routineCompleteYn,
+            subRoutineCompleteYn: $0.subRoutineCompleteYn
+        )
+    }

필요 시 RoutineRepositoryError를 내부 enum으로 정의해 주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func updateRoutineCompletions(routines: [RoutineEntity]) async throws {
let completionDTO = routines.map({ RoutineCompletionDTO(
routineId: $0.routineId,
completeYn: $0.completeYn,
historySeq: $0.historySeq,
routineType: $0.routineType) })
let completionListDTO = RoutineCompletionListDTO(
performedDate: performedDate,
routineCompletionInfos: completionDTO)
routineCompleteYn: $0.routineCompleteYn,
subRoutineCompleteYn: $0.subRoutineCompleteYn) })
let completionListDTO = RoutineCompletionListDTO(routineCompletionInfos: completionDTO)
func updateRoutineCompletions(routines: [RoutineEntity]) async throws {
// 방어: 서브루틴 이름/완료 여부 배열 길이 정합성 확인
try routines.forEach { r in
guard r.subRoutineNames.count == r.subRoutineCompleteYn.count else {
throw RoutineRepositoryError.invalidSubRoutineLength(
routineId: r.routineId,
names: r.subRoutineNames.count,
completes: r.subRoutineCompleteYn.count
)
}
}
let completionDTO = routines.map {
RoutineCompletionDTO(
routineId: $0.routineId,
routineCompleteYn: $0.routineCompleteYn,
subRoutineCompleteYn: $0.subRoutineCompleteYn
)
}
let completionListDTO = RoutineCompletionListDTO(routineCompletionInfos: completionDTO)
// …
}

Comment on lines +127 to +129
func updateAllCompleted() {
allCompletedIcon.isHidden.toggle()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

토글 기반 아이콘 표시 로직은 재바인딩/중복 갱신 시 오동작합니다 — 불리언 인자를 받도록 시그니처 변경 권장

WeekView 등에서 동일 날짜에 대해 여러 번 호출되면 isHidden.toggle()로 인해 실제 완료 상태와 표시가 뒤집힐 수 있습니다. 외부 상태를 그대로 반영하도록 변경해 주세요.

적용 diff:

-    func updateAllCompleted() {
-        allCompletedIcon.isHidden.toggle()
-    }
+    func updateAllCompleted(isCompleted: Bool) {
+        allCompletedIcon.isHidden = !isCompleted
+    }

호출부 예시(다른 파일 적용용):

// WeekView.updateAllCompletedState(allCompletedDates:)
let isCompleted = allCompletedDates.contains(date)
dateView.updateAllCompleted(isCompleted: isCompleted)

다음 스크립트로 기존 호출부를 점검하고 일괄 치환 대상 라인을 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# updateAllCompleted 사용처 확인
rg -nP --type=swift '\bupdateAllCompleted\s*\(' -C2

Length of output: 1567


updateAllCompleted() 토글 로직→명시적 불리언 인자 반영으로 개선 필요

토글 기반의 allCompletedIcon.isHidden.toggle()는 뷰 재사용 또는 중복 호출 시 실제 완료 상태와 표시가 뒤집혀 오동작할 수 있습니다. 외부에서 전달된 완료 여부를 그대로 반영하도록 아래와 같이 리팩터링을 진행해주세요.

• DateView.swift (127~129 줄)

-    func updateAllCompleted() {
-        allCompletedIcon.isHidden.toggle()
-    }
+    func updateAllCompleted(isCompleted: Bool) {
+        allCompletedIcon.isHidden = !isCompleted
+    }

• WeekView.swift 호출부 수정

  1. if isAllCompleted { dateView.updateAllCompleted() } → unconditional call
  2. if allCompletedDates.contains(dateView.key) { … } → unconditional call

예시:

-            let isAllCompleted = allCompletedDates.contains(date)
-            if isAllCompleted {
-                dateView.updateAllCompleted()
-            }
+            let isAllCompleted = allCompletedDates.contains(date)
+            dateView.updateAllCompleted(isCompleted: isAllCompleted)-        for dateView in dateViews {
-            if allCompletedDates.contains(dateView.key) {
-                dateView.value.updateAllCompleted()
-            }
-        }
+        for dateView in dateViews {
+            let isAll = allCompletedDates.contains(dateView.key)
+            dateView.value.updateAllCompleted(isCompleted: isAll)
+        }

이렇게 변경하면 외부 상태에 따라 항상 정확한 아이콘 표시가 보장됩니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func updateAllCompleted() {
allCompletedIcon.isHidden.toggle()
}
func updateAllCompleted(isCompleted: Bool) {
allCompletedIcon.isHidden = !isCompleted
}
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/DateView.swift around lines
127-129, change updateAllCompleted() so it accepts an explicit Bool (e.g.,
updateAllCompleted(_ isHidden: Bool)) and set allCompletedIcon.isHidden =
isHidden instead of toggling; then update the WeekView call sites to call the
method unconditionally with the desired boolean (replace conditional calls like
`if isAllCompleted { dateView.updateAllCompleted() }` and `if
allCompletedDates.contains(dateView.key) { … }` with unconditional calls passing
true/false based on the external state) so the icon always reflects the provided
completion state.

Comment on lines +107 to +115
for (index, subRoutine) in zip(routine.subRoutines, routine.subRoutineCompleted).enumerated() {
let subRoutineView = SubRoutineView(subRoutine: subRoutine, index: index)
routineContentStackView.addArrangedSubview(subRoutineView)
subRoutineView.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(Layout.subRoutineViewHeight)
}
subRoutineView.delegate = self
subRoutineViews[index] = subRoutineView
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

zip + enumerated 사용으로 타입 혼동 및 데이터 손실 위험

  • 현재 루프의 subRoutine(String, Bool) 튜플입니다. SubRoutineView(subRoutine: subRoutine, ...)가 문자열을 기대한다면 컴파일 에러이며, 튜플을 기대하더라도 가독성이 떨어집니다.
  • zip은 두 배열 길이가 다르면 긴 쪽의 잔여 요소가 무시됩니다(데이터 손실).

길이 검증 후 인덱스 기반으로 명시적으로 처리해 주세요. 서브루틴 완료 상태를 서브뷰에 초기 반영하는 것도 포함합니다.

-        for (index, subRoutine) in zip(routine.subRoutines, routine.subRoutineCompleted).enumerated() {
-            let subRoutineView = SubRoutineView(subRoutine: subRoutine, index: index)
+        // 방어: 길이 불일치 시 조기 반환 또는 assert
+        guard routine.subRoutines.count == routine.subRoutineCompleted.count else {
+            assertionFailure("subRoutines.count != subRoutineCompleted.count")
+            return
+        }
+        for index in 0..<routine.subRoutines.count {
+            let name = routine.subRoutines[index]
+            let isCompleted = routine.subRoutineCompleted[index]
+            let subRoutineView = SubRoutineView(subRoutine: name, index: index)
             routineContentStackView.addArrangedSubview(subRoutineView)
             subRoutineView.snp.makeConstraints { make in
                 make.height.greaterThanOrEqualTo(Layout.subRoutineViewHeight)
             }
             subRoutineView.delegate = self
             subRoutineViews[index] = subRoutineView
+            // 초기 완료 상태 반영 (SubRoutineView에 API가 없다면 추가 필요)
+            subRoutineView.setCompleted?(isCompleted)
         }

참고: setCompleted?는 예시입니다. 실제로는 SubRoutineView에 상태 설정용 메서드(예: updateCompletedState(_:))를 제공해 주세요.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/RoutineView.swift around
lines 107–115, the current for-loop uses zip + enumerated which yields (String,
Bool) tuples and can silently drop items if the arrays differ in length; change
to an index-based loop after validating that routine.subRoutines.count ==
routine.subRoutineCompleted.count (or handle mismatches explicitly), then for
each index create SubRoutineView using the string element (not the tuple), set
its completed state immediately via a dedicated API (e.g.,
updateCompletedState(_: ) or an init parameter), apply constraints, assign
delegate, and store the view into subRoutineViews[index]; also add a
guard/assert or fallback behavior when counts mismatch to avoid data loss.

Comment on lines +62 to +66
private func configureLayout() {
addSubview(subRoutineView)
subRoutineView.addSubview(subRoutineCheckButton)
subRoutineView.addSubview(subRoutineLabel)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

내부 컨테이너(subRoutineView) 제약 부족으로 레이아웃 모호 — edges 제약 추가 필요

subRoutineView를 self에 addSubview만 하고 위치/가로세로 제약을 주지 않아 오토레이아웃이 모호합니다. 현재는 높이 제약만 있어 내부 버튼/라벨 제약이 부모에게 전달되지 않습니다.

적용 diff:

-        subRoutineView.snp.makeConstraints { make in
-            make.height.greaterThanOrEqualTo(Layout.subRoutineViewHeight)
-        }
+        subRoutineView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+            make.height.greaterThanOrEqualTo(Layout.subRoutineViewHeight)
+        }

대안: 내부 컨테이너 없이 self에 바로 subRoutineCheckButton/subRoutineLabel을 배치해도 됩니다(구조 단순화).

Also applies to: 79-81

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift around
lines 62-66 (and also apply same fix at lines 79-81), the subRoutineView is
added as a subview but lacks constraints tying its edges to self so Auto Layout
is ambiguous; add explicit edge constraints (top, bottom, leading, trailing or
equivalent anchors) from subRoutineView to its superview so the container has a
defined frame and can propagate constraints of its child views, or alternatively
remove the container and pin subRoutineCheckButton and subRoutineLabel directly
to self to simplify the hierarchy.

private var dateViews: [Date: DateView] = [:]
private let calendar = Calendar.current
private var selectedDate: Date
private var allCompletedDates: [Date] = []
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

날짜 비교 방식 개선 필요: Date 정확도(시간 포함) 비교로 인한 오표시 가능성

[Date]contains는 시각까지 동일해야 true가 됩니다. 모두완료 아이콘은 “날짜 단위” 비교가 의도이므로, calendar.startOfDay(for:)로 정규화한 후 Set<Date>로 보관하는 편이 안전하고 효율적입니다.

적용 예시:

-    private var allCompletedDates: [Date] = []
+    private var allCompletedDates: Set<Date> = []

정규화는 업데이트 시점에 일괄 적용하세요(아래 추가 코멘트 참조).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private var allCompletedDates: [Date] = []
private var allCompletedDates: Set<Date> = []
🤖 Prompt for AI Agents
Projects/Presentation/Sources/Home/View/Component/WeekView.swift around line 27:
현재 allCompletedDates가 [Date]로 시각(시간)까지 포함한 Date를 그대로 보관해 contains 비교 시 동일 시간대가
아니면 누락될 수 있으니, 모든 날짜를 calendar.startOfDay(for:)로 정규화한 후 Set<Date>로 보관하도록 변경하세요;
즉 업데이트(할당) 시점에 incoming Dates를 startOfDay로 변환해 Set으로 초기화하고, 이후 contains 체크는 이
Set을 사용해 효율적이고 날짜 단위 비교가 되도록 구현합니다.

Comment on lines +92 to +96
let isAllCompleted = allCompletedDates.contains(date)
if isAllCompleted {
dateView.updateAllCompleted()
}
dateView.didTapDateButton = { [weak self] date in
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

아이콘 토글 방식은 버그를 유발합니다: 토글(toggle) 대신 상태 설정(set)으로 전환 필요

DateView.updateAllCompleted()가 토글 기반이라, 동일 날짜에 여러 번 호출되면 아이콘이 숨겨졌다가 다시 표시되는 등 비결정적 상태가 됩니다. 항상 “원하는 상태”를 명시적으로 설정하는 API가 필요합니다.

아래처럼 “날짜 정규화 + 명시적 설정”으로 교체해 주세요.

-            let isAllCompleted = allCompletedDates.contains(date)
-            if isAllCompleted {
-                dateView.updateAllCompleted()
-            }
+            let isAllCompleted = allCompletedDates.contains(calendar.startOfDay(for: date))
+            dateView.setAllCompleted(isAllCompleted)

추가로, DateView에 다음 메서드를 도입해 주세요(외부 파일 수정 예시):

// Projects/Presentation/Sources/Home/View/Component/DateView.swift
func setAllCompleted(_ isAllCompleted: Bool) {
    allCompletedIcon.isHidden = !isAllCompleted
}

가능하다면 기존 updateAllCompleted()는 내부(private)로 한정하거나 deprecate 처리하세요.

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/WeekView.swift around lines
92–96, the code calls DateView.updateAllCompleted() which is toggle-based and
causes nondeterministic UI when invoked multiple times; replace this with an
explicit state setter: normalize the date and call a new
DateView.setAllCompleted(Bool) using allCompletedDates.contains(date) so the
icon state is deterministically set. Add the new public setter to
Projects/Presentation/Sources/Home/View/Component/DateView.swift: func
setAllCompleted(_ isAllCompleted: Bool) { allCompletedIcon.isHidden =
!isAllCompleted } and make the old updateAllCompleted() private or mark it
deprecated to prevent future toggle usage.

Comment on lines +108 to +115
func updateAllCompletedState(allCompletedDates: [Date]) {
self.allCompletedDates = allCompletedDates
for dateView in dateViews {
if allCompletedDates.contains(dateView.key) {
dateView.value.updateAllCompleted()
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

상태 동기화 누락: 해제(false) 경로가 없어 아이콘이 계속 남습니다 · 성능/가독성 개선 포함

현재는 포함된 날짜에만 토글 호출을 하므로, “완료 → 미완료”로 바뀐 날짜의 아이콘이 해제되지 않습니다. 날짜별로 true/false를 모두 반영해야 합니다. 또한 SwiftLint 제안대로 for-where를 활용하면 가독성이 좋아집니다.

적용 예시:

-    func updateAllCompletedState(allCompletedDates: [Date]) {
-        self.allCompletedDates = allCompletedDates
-        for dateView in dateViews {
-            if allCompletedDates.contains(dateView.key) {
-                dateView.value.updateAllCompleted()
-            }
-        }
-    }
+    func updateAllCompletedState(allCompletedDates: [Date]) {
+        // 날짜 단위로 정규화하여 Set에 보관
+        self.allCompletedDates = Set(allCompletedDates.map { calendar.startOfDay(for: $0) })
+        // 모든 DateView에 대해 상태를 명시적으로 반영
+        for (date, view) in dateViews {
+            let isAllCompleted = self.allCompletedDates.contains(calendar.startOfDay(for: date))
+            view.setAllCompleted(isAllCompleted)
+        }
+    }

추가 미세개선(선택):

  • 주석의 “for-where” 경고를 반영하려면 두 번째 루프를 분기 두 개의 for-where로 나누어도 됩니다.

위 변경 후 기존 토글 API 사용처를 전수 교체했는지 확인해 주세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "🔎 토글 API 사용처 검색"
rg -n --type swift 'updateAllCompleted\s*\(' -C2 || true

echo "🔎 새 API 사용처 검색"
rg -n --type swift 'setAllCompleted\s*\(' -C2 || true

Length of output: 1723


WeekView.swift에서 기존 API 호출(updateAllCompleted) 미교체: 전수 교체가 필요합니다

현재 updateAllCompleted() 호출이 여전히 남아 있어 “완료 → 미완료” 전환 시 아이콘이 해제되지 않습니다. 또한 새로 제안된 setAllCompleted(_:) API가 코드베이스 어디에도 사용되지 않고 있습니다.

수정이 필요한 위치:

  • Projects/Presentation/Sources/Home/View/Component/WeekView.swift
    • 94행: dateView.updateAllCompleted()
    • 112행: dateView.value.updateAllCompleted()
  • Projects/Presentation/Sources/Home/View/Component/DateView.swift
    • 127행: func updateAllCompleted() { … } (toggle 구현 → setAllCompleted(_:) 추가 및 기존 메서드 제거)

제안된 변경(diff 예시):

 func updateAllCompletedState(allCompletedDates: [Date]) {
-    self.allCompletedDates = allCompletedDates
-    for dateView in dateViews {
-        if allCompletedDates.contains(dateView.key) {
-            dateView.value.updateAllCompleted()
-        }
-    }
+    // 날짜 단위로 정규화하여 Set에 보관
+    self.allCompletedDates = Set(allCompletedDates.map { calendar.startOfDay(for: $0) })
+    // 모든 DateView에 대해 상태 명시적 반영
+    for (date, view) in dateViews where true {
+        let isCompleted = self.allCompletedDates.contains(calendar.startOfDay(for: date))
+        view.setAllCompleted(isCompleted)
+    }
 }
 extension DateView {
-    func updateAllCompleted() {
-        allCompletedIcon.isHidden.toggle()
-    }
+    /// 전체 완료 여부에 따라 아이콘 표시 상태를 설정합니다.
+    func setAllCompleted(_ completed: Bool) {
+        allCompletedIcon.isHidden = !completed
+    }
 }

수정 후, 아래 스크립트를 통해 모든 호출이 정상 교체되었는지 반드시 확인해 주세요.

#!/bin/bash
set -euo pipefail

echo "🔎 기존 API(updateAllCompleted) 호출 검색"
rg -n --type swift 'updateAllCompleted\s*\(' -C2 || echo "⚠️ 호출 없음 확인됨"
echo "🔎 새 API(setAllCompleted) 호출 검색"
rg -n --type swift 'setAllCompleted\s*\(' -C2 || echo "⚠️ 호출 없음"
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 111-111: where clauses are preferred over a single if inside a for

(for_where)

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/WeekView.swift around lines
94 and 112, calls to dateView.updateAllCompleted() must be replaced so they call
setAllCompleted(_:) with an explicit Bool (true when the date is contained in
allCompletedDates, false otherwise); update the loop at 108–115 to call
dateView.value.setAllCompleted(true) for contained dates and ensure
non-contained dateViews receive setAllCompleted(false). In
Projects/Presentation/Sources/Home/View/Component/DateView.swift around line
127, remove the toggle-style func updateAllCompleted() and implement func
setAllCompleted(_ completed: Bool) that explicitly sets the completed UI state
(no toggling) and updates the icon/appearance accordingly. After making changes,
run the provided grep script to verify there are no remaining
updateAllCompleted(...) calls and that setAllCompleted(...) is present
everywhere.

@choijungp choijungp merged commit b3f9d55 into develop Aug 22, 2025
2 checks passed
@choijungp choijungp deleted the feat/routine-completion branch August 22, 2025 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant