Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
//

struct RoutineCompletionListDTO: Encodable {
let performedDate: String
let routineCompletionInfos: [RoutineCompletionDTO]
}

struct RoutineCompletionDTO: Encodable {
let routineId: String
let completeYn: Bool
let historySeq: Int
let routineType: String
let routineCompleteYn: Bool
let subRoutineCompleteYn: [Bool]
}
4 changes: 3 additions & 1 deletion Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ extension RoutineEndpoint: Endpoint {

var method: HTTPMethod {
switch self {
case .createRoutine, .updateRoutineCompletion:
case .createRoutine:
.post
case .updateRoutineCompletion:
.put
case .fetchRoutine, .fetchRoutines:
.get
case .updateRoutine:
Expand Down
14 changes: 4 additions & 10 deletions Projects/DataSource/Sources/Repository/RoutineRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,12 @@ final class RoutineRepository: RoutineRepositoryProtocol {
_ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self)
}

func updateRoutineCompletions(routines: [RoutineCompletionEntity]) async throws {
guard let routine = routines.first else { return }
let performedDate = routine.performedDate
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)

Comment on lines +77 to 83

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)
// …
}

let endpoint = RoutineEndpoint.updateRoutineCompletion(routines: completionListDTO)
_ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self)
Expand Down
28 changes: 0 additions & 28 deletions Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ public protocol RoutineRepositoryProtocol {

/// 루틴 완료 여부를 업데이트 합니다.
/// - Parameter routines: 완료 여부를 업데이트할 루틴 배열
func updateRoutineCompletions(routines: [RoutineCompletionEntity]) async throws
func updateRoutineCompletions(routines: [RoutineEntity]) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ public protocol RoutineUseCaseProtocol {

func deleteDailyRoutine(routineId: String) async throws

func updateRoutineCompletions(routines: [RoutineCompletionEntity]) async throws
func updateRoutineCompletions(routines: [RoutineEntity]) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public final class RoutineUseCase: RoutineUseCaseProtocol {
try await routineRepository.deleteDailyRoutine(routineId: routineId)
}

public func updateRoutineCompletions(routines: [RoutineCompletionEntity]) async throws {
public func updateRoutineCompletions(routines: [RoutineEntity]) async throws {
try await routineRepository.updateRoutineCompletions(routines: routines)
}
}
2 changes: 1 addition & 1 deletion Projects/Presentation/Sources/Common/View/TabBarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final public class TabBarView: UITabBarController {
guard let mypageViewModel = DIContainer.shared.resolve(type: MypageViewModel.self)
else { fatalError("mypageViewModel 의존성이 등록되지 않았습니다.") }

let homeView = HomeView(viewModel: homeViewModel)
let homeView = HomeViewController(viewModel: homeViewModel)
let recommendView = RecommendedRoutineViewController(viewModel: recommendedRoutineViewModel)
let mypageView = MypageView(viewModel: mypageViewModel)

Expand Down
15 changes: 15 additions & 0 deletions Projects/Presentation/Sources/Home/Model/Routine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ struct Routine: RoutineProtocol {
let isDeleted: Bool
let startDate: Date
let endDate: Date

func toRoutineEntity() -> RoutineEntity {
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))
}
}

extension RoutineEntity {
Expand Down
19 changes: 17 additions & 2 deletions Projects/Presentation/Sources/Home/View/Component/DateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ final class DateView: UIView {
static let dateButtonTopSpacing: CGFloat = 7
static let dateButtonSize: CGFloat = 30
static let dateLabelHeight: CGFloat = 17
static let allCompletedIconTopSpacing: CGFloat = 5
}

private let dayLabel = UILabel()
private let dateButton = UIButton()
private let dateLabel = UILabel()
private let allCompletedIcon = UIImageView()
private let date: Date
private let isToday: Bool
private var isSelected: Bool {
didSet {
updateAttribute()
}
}
var didTappedDateButton: ((Date) -> Void)?
var didTapDateButton: ((Date) -> Void)?

init(
date: Date,
Expand Down Expand Up @@ -66,12 +68,16 @@ final class DateView: UIView {
dateLabel.text = "\(date.convertToString(dateType: .date))"
dateLabel.font = BitnagilFont(style: .body2, weight: .medium).font
dateLabel.textColor = BitnagilColor.gray70

allCompletedIcon.image = BitnagilIcon.asteriskIcon
allCompletedIcon.isHidden = true
}

private func configureLayout() {
addSubview(dayLabel)
addSubview(dateButton)
dateButton.addSubview(dateLabel)
addSubview(allCompletedIcon)

dayLabel.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
Expand All @@ -89,6 +95,11 @@ final class DateView: UIView {
make.center.equalToSuperview()
make.height.equalTo(Layout.dateLabelHeight)
}

allCompletedIcon.snp.makeConstraints { make in
make.top.equalTo(dateButton.snp.bottom).offset(Layout.allCompletedIconTopSpacing)
make.centerX.equalToSuperview()
}
}

private func updateAttribute() {
Expand All @@ -106,10 +117,14 @@ final class DateView: UIView {
}

private func selectDate() {
didTappedDateButton?(date)
didTapDateButton?(date)
}

func updateSelectState(isSelected: Bool) {
self.isSelected = isSelected
}

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

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.

}

This file was deleted.

Loading
Loading