Conversation
Walkthrough루틴 생성/수정 흐름을 단일 엔티티(RoutineCreationEntity) 중심으로 통합하고 DTO/Endpoint를 v2로 맞추며, Presentation에서는 기간을 옵셔널로 처리하고 유효성 바인딩을 추가했습니다. Domain에 RoutineCreationEntity와 RoutineUpdateApplyDateType이 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant V as ViewController
participant VM as ViewModel
participant UC as UseCase
participant RP as Repository
participant EP as Endpoint
participant API as Routines API
V->>VM: 사용자 입력(이름/반복/기간/시간/추천유형 등)
VM->>VM: 유효성 검사 (이름, 시작/종료, 시간)
V->>VM: 등록 버튼 탭
VM->>UC: saveRoutine(routine: RoutineCreationEntity)
alt routine.id == nil
UC->>RP: createRoutine(routine)
RP->>EP: createRoutine(RoutineCreationDTO) @ v2
EP->>API: POST /api/v2/routines
else
UC->>RP: updateRoutine(routine)
RP->>EP: updateRoutine(RoutineCreationDTO) @ v2
EP->>API: PATCH /api/v2/routines/{id}
end
API-->>EP: 응답
EP-->>RP: 전달
RP-->>UC: 전달
UC-->>VM: 완료 이벤트
VM-->>V: UI 업데이트 / 네비게이션
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift (1)
48-51: 초기화 시 임의 날짜 주입이 새 플레이스홀더 로직을 무력화합니다init에서 현재 날짜로 버튼 타이틀을 강제로 설정하고 있어(“TODO: 추후 삭제”) nil일 때 플레이스홀더(“시작 일자 설정”, “종료 일자 설정”)가 표시되지 않습니다. 실제 동작이 기획과 반대로 보일 수 있으므로 즉시 제거를 권장합니다.
아래 변경으로 임의 주입을 제거하세요:
// TODO: - 추후 삭제 - configureButon(buttonType: .start, date: Date()) - configureButon(buttonType: .end, date: Date()) + // 초기 타이틀은 configure(dependency:)에서 설정됩니다.Also applies to: 68-80
🧹 Nitpick comments (18)
Projects/Domain/Sources/Entity/Enum/RoutineUpdateApplyDateType.swift (1)
9-12: Codable/CaseIterable 채택으로 직렬화·유틸리티성 강화 제안서버와의 바인딩에 rawValue를 사용 중이므로 Codable 채택을 추가하면 테스트/디버깅/스토리지 연계에 유리합니다. CaseIterable은 화면 선택지 노출 등에 유용합니다.
아래처럼 확장하는 것을 권장합니다:
-public enum RoutineUpdateApplyDateType: String { +public enum RoutineUpdateApplyDateType: String, Codable, CaseIterable { case today = "TODAY" case tomorrow = "TOMORROW" }Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift (1)
153-158: 오타: configureButon → configureButton (명명 일관성)메서드명 오타는 오랜 기간 누락되기 쉽고 검색성/가독성을 해칩니다. 내부 사용만 예상되더라도 정정 권장합니다.
- func configureButon(buttonType: ButtonType, date: Date) { + func configureButton(buttonType: ButtonType, date: Date) { let button = buttonType == .start ? startButton : endButton let dateString = date.convertToString(dateType: .yearMonthDate) button.setTitle(dateString, for: .normal) }호출부가 외부에 존재한다면 리네이밍 대응도 함께 필요합니다. 원하시면 검색 스크립트를 드리겠습니다.
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (2)
75-81: 중복 제거: 생성/수정 바디 매핑 공통화create/update 모두 동일한 DTO.dictionary를 반환하므로 하나로 묶어 간결화 가능합니다.
var bodyParameters: [String : Any] { switch self { - case .createRoutine(let routine): - return routine.dictionary - case .updateRoutine(let routine): - return routine.dictionary + case .createRoutine(let routine), .updateRoutine(let routine): + return routine.dictionary case .deleteDailyRoutine(let routine): return routine.dictionary case .updateRoutineCompletion(let routines): return routines.dictionary default: return [:] } }
56-62: 헤더 accept 값 명확화 제안서버가 JSON을 반환한다면 accept를 “application/json”으로 제한하는 편이 의도와 보안(콘텐츠 협상) 측면에서 명확합니다.
- let headers: [String: String] = [ - "Content-Type": "application/json", - "accept": "*/*" - ] + let headers: [String: String] = [ + "Content-Type": "application/json", + "accept": "application/json" + ]Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
31-47: 가독성 개선(변수 섀도잉 제거) + 생성 시 applyDateType 제거 로직 권장
- 매개변수와 동일한 이름의 지역변수(routine) 재정의는 가독성을 떨어뜨립니다. 명확한 이름으로 변경을 권장합니다.
- updateApplyDate는 “수정”에만 의미가 있을 가능성이 높습니다. 생성(create) 시에는 nil로 강제하여 불필요한 필드 전송을 피하는 것이 안전합니다(백엔드 스펙과 상이할 경우 400 방지).
아래처럼 정리하면 의도가 명확해집니다:
- public func saveRoutine(routine: RoutineCreationEntity) async throws { - let routine = RoutineCreationEntity( + public func saveRoutine(routine: RoutineCreationEntity) async throws { + let sanitizedRoutine = RoutineCreationEntity( id: routine.id, name: routine.name, repeatDay: routine.repeatDay, startDate: routine.startDate, endDate: routine.endDate, executionTime: routine.executionTime, subroutines: routine.subroutines.filter { !$0.isEmpty }, recommendedRoutineType: routine.recommendedRoutineType, - applyDateType: routine.applyDateType) + applyDateType: routine.id == nil ? nil : routine.applyDateType) - if routine.id == nil { // 루틴 아이디가 있으면 수정, 없으면 생성 - try await createRoutine(routine: routine) - } else { - try await updateRoutine(routine: routine) - } + // 루틴 아이디가 없으면 생성, 있으면 수정 + if sanitizedRoutine.id == nil { + try await createRoutine(routine: sanitizedRoutine) + } else { + try await updateRoutine(routine: sanitizedRoutine) + } }추가로, 실제 서버 스펙이 “생성 시 updateApplyDate 허용/무시”인지 확인 부탁드립니다. 필요 시 Repository에서 생성 DTO의 updateApplyDate를 무조건 nil로 설정하도록 강제하는 것도 고려해볼 수 있습니다.
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (1)
28-30: updateRoutine의 계약(Contract) 명시 필요수정 시 routine.id가 필수임을 문서 주석에 명확히 드러내면 사용 측 혼란을 줄일 수 있습니다.
적용 제안:
- /// - routine: 수정할 루틴 + /// - routine: 수정할 루틴 (id는 필수) func updateRoutine(routine: RoutineCreationEntity) async throwsProjects/DataSource/Sources/DTO/RoutineCreationDTO.swift (1)
22-37: nil 처리 개선 및 누락된 매핑 보완 제안
- recommendedRoutineType: nil일 때 빈 문자열을 넘기는 방식보다 flatMap 초기화를 쓰는 편이 안전합니다.
- updateApplyDate: 현재 Entity의 applyDateType을 항상 nil로 고정하고 있어 응답 → Domain 변환 시 정보가 소실됩니다. 필요 시 타입 매핑을 추가하세요.
다음과 같이 매핑을 개선할 수 있습니다:
extension RoutineCreationDTO { func toRoutineCreationEntity() -> RoutineCreationEntity { let weeks = repeatDay.compactMap { Week(rawValue: $0) } - let routineCategoryType = RoutineCategoryType(rawValue: recommendedRoutineType ?? "") + let routineCategoryType = recommendedRoutineType.flatMap { RoutineCategoryType(rawValue: $0) } + let applyDateType = updateApplyDate.flatMap { RoutineUpdateApplyDateType(rawValue: $0) } return RoutineCreationEntity( id: routineId, name: routineName, repeatDay: weeks, startDate: routineStartDate, endDate: routineEndDate, executionTime: executionTime, subroutines: subRoutineName, recommendedRoutineType: routineCategoryType, - applyDateType: nil) + applyDateType: applyDateType) } }Projects/DataSource/Sources/Repository/RoutineRepository.swift (2)
13-24: DTO 구성 중복 제거 제안create/update 모두 동일한 DTO 구성 로직이 반복됩니다. private 헬퍼로 추출하면 유지보수성이 좋아집니다.
다음처럼 교체를 제안합니다:
- let routineCreationDTO = RoutineCreationDTO( - routineId: nil, - updateApplyDate: routine.applyDateType?.rawValue, - routineName: routine.name, - repeatDay: routine.repeatDay.map { $0.rawValue }, - routineStartDate: routine.startDate, - routineEndDate: routine.endDate, - executionTime: routine.executionTime, - subRoutineName: routine.subroutines, - recommendedRoutineType: routine.recommendedRoutineType?.rawValue) + let routineCreationDTO = buildRoutineCreationDTO(from: routine, includeId: false)헬퍼 추가(파일 내 적절한 위치):
private extension RoutineRepository { func buildRoutineCreationDTO(from routine: RoutineCreationEntity, includeId: Bool) -> RoutineCreationDTO { RoutineCreationDTO( routineId: includeId ? routine.id : nil, updateApplyDate: routine.applyDateType?.rawValue, routineName: routine.name, repeatDay: routine.repeatDay.map { $0.rawValue }, routineStartDate: routine.startDate, routineEndDate: routine.endDate, executionTime: routine.executionTime, subRoutineName: routine.subroutines, recommendedRoutineType: routine.recommendedRoutineType?.rawValue ) } }
49-59: updateRoutine 호출 시 id 사전 검증(방어적 코드)UseCase에서 분기하고 있어 실사용상 문제는 없겠지만, Repository 레이어에서도 id가 없는 업데이트 호출을 방지하는 방어 로직을 권장합니다.
다음 한 줄을 DTO 구성 앞에 추가:
func updateRoutine(routine: RoutineCreationEntity) async throws { + precondition(routine.id != nil, "updateRoutine(routine:) 호출 시 id는 필수입니다.") let routineUpdateDTO = RoutineCreationDTO( routineId: routine.id, updateApplyDate: routine.applyDateType?.rawValue, routineName: routine.name, repeatDay: routine.repeatDay.map { $0.rawValue }, routineStartDate: routine.startDate, routineEndDate: routine.endDate, executionTime: routine.executionTime, subRoutineName: routine.subroutines, recommendedRoutineType: routine.recommendedRoutineType?.rawValue)또한, 위의 중복 제거 헬퍼를 사용한다면:
- let routineUpdateDTO = RoutineCreationDTO( ... ) + let routineUpdateDTO = buildRoutineCreationDTO(from: routine, includeId: true)Projects/Domain/Sources/Entity/RoutineCreationEntity.swift (1)
21-31: 초기화 편의성 개선(기본값 부여) 제안id/recommendedRoutineType/applyDateType는 선택적 성격이 강하므로 기본값(nil)과 subroutines의 기본값([])을 제공하면 호출부가 간결해집니다. 테스트 작성에도 유리합니다.
- public init( - id: String?, + public init( + id: String? = nil, name: String, repeatDay: [Week], startDate: String, endDate: String, executionTime: String, - subroutines: [String], - recommendedRoutineType: RoutineCategoryType?, - applyDateType: RoutineUpdateApplyDateType? + subroutines: [String] = [], + recommendedRoutineType: RoutineCategoryType? = nil, + applyDateType: RoutineUpdateApplyDateType? = nil ) {추가로, Equatable/Sendable 컨폼을 검토하면 테스트/동시성 안전성에 도움이 됩니다.
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (1)
50-51: 기간 유효성 추가 검증 권장(시작 ≤ 종료)현재는 nil 여부만 확인합니다. 시작일이 종료일보다 늦은 경우를 방지하는 검증을 추가하세요.
아래와 같이 updateIsRoutineValid 내 조건을 보강할 수 있습니다:
private func updateIsRoutineValid() { guard let name = nameSubject.value, !name.isEmpty, executionTimeSubject.value.startAt != nil, - periodStartSubject.value != nil, - periodEndSubject.value != nil + let start = periodStartSubject.value, + let end = periodEndSubject.value, + start <= end else { checkRoutinePublisher.send(false) return } checkRoutinePublisher.send(true) }Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift (7)
76-77: 조건식 간결화: updateInfo?.routineId == nil 대신 updateInfo == nil 사용 권장튜플의 routineId가 비옵셔널이므로 옵셔널 체이닝이 불필요합니다. 의도를 더 명확히 표현해 주세요.
적용 diff:
- navigationTitle = updateInfo?.routineId == nil ? "루틴 등록" : "루틴 수정" - registerButtonTitle = updateInfo?.routineId == nil ? "등록하기" : "수정하기" + navigationTitle = updateInfo == nil ? "루틴 등록" : "루틴 수정" + registerButtonTitle = updateInfo == nil ? "등록하기" : "수정하기"
93-95: 중복된 버튼 타이틀 설정 제거 제안두 이니셜라이저에서 동일한
registerButton.setTitle호출이 반복됩니다. 중복 제거를 위해 private 메서드로 추출하거나 공통 초기화 경로에서 한 번만 세팅하는 편이 깔끔합니다.원하시면
applyStaticTexts()같은 헬퍼로 정리한 diff를 제안드릴게요.
113-118: 텍스트필드 UX/접근성 보완 (returnKey/접근성 식별자 추가)엔터 키 타입과 접근성 식별자를 지정하면 UX와 UI 테스트 신뢰도가 개선됩니다.
적용 diff:
nameTextField.attributedPlaceholder = NSAttributedString(string: "루틴 제목을 입력해주세요.", attributes: attributes) nameTextField.font = BitnagilFont(style: .title3, weight: .semiBold).font nameTextField.textColor = BitnagilColor.gray10 + nameTextField.returnKeyType = .done + nameTextField.accessibilityIdentifier = "routineNameTextField" nameTextField.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged) nameTextField.delegate = self
131-135: 중복 탭 방지: 등록 버튼 탭 시 즉시 비활성화 권장네트워크 요청 동안 빠른 연속 탭으로 중복 등록이 발생할 수 있습니다. 탭 시점을 기준으로 즉시 버튼을 비활성화하고, 완료/에러 시 다시 활성화하는 흐름을 추천합니다. 완료/에러 통지는 ViewModel의 별도 output(예:
isRegistering또는registerCompleted)으로 처리하는 것이 가장 깔끔합니다.예시 diff(비활성화만 선반영 — 재활성화는 ViewModel output과 바인딩 필요):
registerButton.addAction( UIAction { [weak self] _ in - self?.viewModel.action(input: .registerRoutine) + guard let self = self else { return } + self.registerButton.isEnabled = false + self.viewModel.action(input: .registerRoutine) }, for: .touchUpInside)추가로, ViewModel에
output.isRegistering가 있다면bind()에서 해당 값을 받아registerButton.isEnabled와 배경색을 토글하도록 도와드릴 수 있습니다.
145-148: 클리어 버튼은 UITextField.rightView 활용을 권장현재 클리어 버튼이 별도 서브뷰로 오버레이되어 있어 텍스트가 버튼 아래로 흐를 수 있습니다.
UITextField.rightView를 사용하면 터치 영역/레이아웃 관리가 더 안전하고, 접근성/포커스 이동도 일관됩니다.예시(대체안):
nameTextField.rightView = clearButton nameTextField.rightViewMode = .whileEditing // 오토레이아웃 대신 버튼 자체 크기만 지정 clearButton.snp.remakeConstraints { make in make.size.equalTo(Layout.clearButtonSize) }원하시면 이 방식으로 레이아웃/속성 변경까지 포함한 전체 diff를 제안드릴게요.
Also applies to: 170-174
211-217: IME/커서 점프 방지: 동일 값이면 text 세팅 생략입력 중 ViewModel -> View 바인딩이 같은 값으로 다시 설정되면 한글/일본어 입력 IME에서 조합이 끊기는 등 UX 이슈가 생길 수 있습니다. 값이 변한 경우에만 적용해 주세요.
적용 diff:
- .sink { [weak self] name in - self?.nameTextField.text = name - } + .sink { [weak self] name in + guard let self = self else { return } + if self.nameTextField.text != name { + self.nameTextField.text = name + } + }
274-287: 초기 버튼 상태 플리커 방지: 초기 비활성화 스타일 지정 권장유효성 퍼블리셔가 최초 방출되기 전까지 버튼의 초기 상태/스타일이 미정일 수 있습니다.
configureAttribute()에서 기본을 비활성화(회색 배경)로 명시해두면 플리커/오동작을 줄일 수 있습니다.예시(추가 코드 — configureAttribute 내 초기화 시점에 배치):
registerButton.isEnabled = false registerButton.backgroundColor = BitnagilColor.gray95 registerButton.setTitleColor(.white, for: .normal)원하시면 해당 위치에 정확히 삽입할 수 있도록 패치도 제안드리겠습니다.
📜 Review details
Configuration used: .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.
📒 Files selected for processing (11)
Projects/DataSource/Sources/DTO/RoutineCreationDTO.swift(1 hunks)Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift(1 hunks)Projects/DataSource/Sources/Repository/RoutineRepository.swift(2 hunks)Projects/Domain/Sources/Entity/Enum/RoutineUpdateApplyDateType.swift(1 hunks)Projects/Domain/Sources/Entity/RoutineCreationEntity.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift(2 hunks)Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift(1 hunks)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift(2 hunks)Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift(8 hunks)Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift(6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 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/RoutineCreation/View/RoutineCreationViewController.swift
🧬 Code Graph Analysis (7)
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (1)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
saveRoutine(31-48)
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (2)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (2)
createRoutine(13-28)updateRoutine(49-63)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (2)
createRoutine(50-52)updateRoutine(54-56)
Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift (1)
Projects/Shared/Sources/Extension/Date+.swift (1)
convertToString(16-22)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (2)
createRoutine(13-28)updateRoutine(49-63)
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (2)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (2)
updateRoutine(49-63)createRoutine(13-28)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (2)
updateRoutine(54-56)createRoutine(50-52)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (2)
createRoutine(50-52)updateRoutine(54-56)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (2)
Projects/Shared/Sources/Extension/Date+.swift (1)
convertToString(16-22)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
saveRoutine(31-48)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift
[Warning] 134-134: TODOs should be resolved (- routine 엔티티 변경 이후 시작일자, 종료 일...)
(todo)
🔇 Additional comments (14)
Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift (1)
33-34: 의존성 dates 옵셔널화 방향 OK빈 날짜 상태를 표현하기 위한 옵셔널 전환이 UI/도메인 변경사항과 일관됩니다. ViewModel/바인딩 쪽에서도 nil 상태가 정상적으로 전달되는지만 함께 확인해 주세요.
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (1)
15-15: saveRoutine 시그니처 변경 호출부 일괄 확인 완료
rg검색 결과, 구 시그니처 호출이나 관련 잔존 토큰이 없습니다. 모두func saveRoutine(routine: RoutineCreationEntity) async throws형태로 업데이트된 것을 확인했습니다.Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (2)
12-12: 업데이트 DTO 교체(…UpdateDTO → RoutineCreationDTO) 방향 OK업데이트도 생성과 동일한 페이로드 구조를 사용하도록 통합한 점 일관성 좋습니다. 서버 스펙과 필드명이 정확히 일치하는지만 한 번 더 확인 부탁드립니다.
20-25: 절대 URL 사용 방식 확인 완료
URLRequest(urlString:)초기화에서URLComponents(string:)로 절대 URL을 파싱하므로,path에 이미 포함된AppProperties.baseURL을 그대로 넘겨도 중복 호스트나 잘못된 URL 생성 이슈가 없습니다.Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
50-56: Repository 위임 로직 단순·명확, OKcreate/update를 각각 1:1 위임하는 구조로 정리되어 이해하기 쉽습니다.
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (1)
12-14: 단일 엔티티로의 전환(LGTM)createRoutine가 RoutineCreationEntity 하나로 통합된 방향성 좋습니다. UseCase/Repository 전반의 API와 일관적입니다.
Projects/DataSource/Sources/DTO/RoutineCreationDTO.swift (2)
8-9: Domain 의존성 추가(LGTM)DTO ↔ Domain 매핑을 위한 Domain 모듈 import 적절합니다.
11-20: 필드 추가 정렬(LGTM)서버 페이로드 필드와 Domain 엔티티에 필요한 값들이 잘 정렬되었습니다.
Projects/Domain/Sources/Entity/RoutineCreationEntity.swift (1)
10-42: 엔티티 도입 방향성 적절(LGTM)생성/수정 공용 페이로드로 RoutineCreationEntity를 둔 설계가 간단하고 명확합니다.
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (4)
41-43: 기간 옵셔널화(LGTM)periodPublisher를 (Date?, Date?)로 변경한 것은 UI 유효성 판단과 일치합니다. 다운스트림 구독자들이 옵셔널 대응하도록 확인만 부탁드립니다.
260-271: RoutineCreationEntity 구성(LGTM)
- repeatDay 계산, 기간/시간 문자열화, 추천 루틴 타입/적용일자 전달 등 저장 페이로드 작성 흐름이 명확합니다. UseCase에서 공백 서브루틴 필터링도 보완되어 있어 안정적입니다.
247-250: 서버 포맷 확인: DateType.yearMonthDate("yyyy-MM-dd"), DateType.time("HH:mm:ss")
현재 코드에서
.yearMonthDate→"yyyy-MM-dd".time→"HH:mm:ss"
로 설정되어 있습니다.
서버 API가 위 포맷을 정확히 기대하는지 다시 한 번 확인해 주세요.
불일치 시 DateType 또는 formatString을 조정해 주시기 바랍니다.
134-135: RoutineEntity에 startDate·endDate 필드 없어 TODO 적용 불가
Domain/Sources/Entity/RoutineEntity.swift에 시작·종료일(startDate/endDate)이 정의되어 있지 않습니다.RoutineResponseDTO역시 날짜 정보가 포함되어 있지 않아 ViewModel(fetchRoutine)에서periodStartSubject/periodEndSubject를 설정할 수 없습니다.- 만약 UI에서 기존 루틴의 기간을 보여줘야 한다면, 백엔드 API와 DTO/Entity에 해당 날짜 필드를 추가한 뒤 매핑 로직을 구현해야 합니다.
- 현 시점에서는 해당 TODO 주석을 제거하거나, 별도 이슈로 등록해 백엔드 지원 후 처리하시기 바랍니다.
Likely an incorrect or invalid review comment.
Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift (1)
120-127: 클리어 액션이 ViewModel과 동기화되는 점 좋습니다텍스트 삭제 후 즉시
.configureName("")를 호출하여 상태를 일관되게 유지하는 부분이 좋습니다.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (1)
23-24: updateType 주입 경로 추가로 applyDateType 미전달 이슈 해결
.configureUpdateType인풋과 해당 분기 처리가 추가되어 ViewController에서 받은 업데이트 적용 시점을 VM에 정상 전달할 수 있게 되었습니다. 지난 코멘트에서 제기된 누락 문제를 해결합니다.Also applies to: 80-84
Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift (1)
72-85: updateInfo(updateType) → ViewModel 전달까지 일관 연결 완료초기화 시
updateInfo에 따라 fetch 호출과configureUpdateType전달을 모두 수행하여 수정 플로우의 “적용 시점”이 유실되지 않습니다. 지난 리뷰 지적 사항을 잘 반영했습니다.
🧹 Nitpick comments (2)
Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift (2)
132-136: 등록 버튼의 초기 상태를 명시적으로 비활성화하여 깜빡임/초기 스타일 불일치 방지초기 진입 시점에는 아직
isRoutineValid발행 전일 수 있으므로 버튼을 기본 disabled 스타일로 세팅해 두는 것이 안전합니다.아래처럼 초기 상태를 추가해 주세요.
registerButton.titleLabel?.font = BitnagilFont.init(style: .body1, weight: .semiBold).font +// 초기 상태: 비활성화 +registerButton.isEnabled = false +registerButton.backgroundColor = BitnagilColor.gray95 +registerButton.setTitleColor(.white, for: .disabled) registerButton.addAction( UIAction { [weak self] _ in self?.viewModel.action(input: .registerRoutine) }, for: .touchUpInside)
275-288: UIButton 상태별 타이틀 색상 지정 누락(Disabled 상태 반영 필요)현재
.normal상태 색상만 설정하고 있어, disabled 시 시스템 기본 색으로 보일 수 있습니다. 의도대로 비활성화 시에도 흰색 유지가 필요하면.disabled에 대해서도 지정해 주세요.아래처럼 두 분기 모두에
.disabled색을 추가하는 방식을 권장합니다.if isRoutineValid { self?.registerButton.isEnabled = true self?.registerButton.backgroundColor = BitnagilColor.gray10 self?.registerButton.setTitleColor(.white, for: .normal) + self?.registerButton.setTitleColor(.white, for: .disabled) } else { self?.registerButton.isEnabled = false self?.registerButton.backgroundColor = BitnagilColor.gray95 self?.registerButton.setTitleColor(.white, for: .normal) + self?.registerButton.setTitleColor(.white, for: .disabled) }
📜 Review details
Configuration used: .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.
📒 Files selected for processing (3)
Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationCardView.swift(1 hunks)Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift(11 hunks)Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift(8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 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/RoutineCreation/View/RoutineCreationViewController.swift
🧬 Code Graph Analysis (3)
Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationCardView.swift (4)
Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift (1)
configure(67-81)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineRepeatContentView.swift (1)
configure(63-67)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimeContentView.swift (1)
configure(65-79)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineNameContentView.swift (1)
configure(73-92)
Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift (8)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (4)
action(80-112)fetchRoutine(114-150)configureName(169-171)registerRoutine(239-279)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
fetchRoutine(18-21)Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
fetchRoutine(30-35)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineNameContentView.swift (2)
textFieldEditingChanged(242-244)configure(73-92)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationCardView.swift (3)
configure(69-76)configure(179-204)configure(206-208)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift (1)
configure(67-81)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineRepeatContentView.swift (1)
configure(63-67)Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimeContentView.swift (1)
configure(65-79)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (2)
Projects/Shared/Sources/Extension/Date+.swift (1)
convertToString(16-22)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
saveRoutine(31-48)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift
[Warning] 137-137: TODOs should be resolved (- routine 엔티티 변경 이후 시작일자, 종료 일...)
(todo)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationCardView.swift (1)
206-207: 의도된 파라미터명/타입 변경 잘 반영됨
configure(dependency:)로의 시그니처/호출 정리가ContentView.Dependency흐름과 일관됩니다. 다른 ContentView들과의 인터페이스도 맞춰졌습니다.Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (3)
42-42: 기간 옵셔널화 및 Publisher 시그니처 변경 적절
periodPublisher를(Date?, Date?)로, 내부 subject를Date?로 바꾼 점은 UI의 플레이스홀더/비선택 상태를 표현하기에 적합합니다.Also applies to: 51-53
224-231: 기간을 제출 유효성의 필수 항목으로 강제 — 요구사항 최종 확인 요청현재는 이름/시간/시작일/종료일이 모두 채워져야만 버튼이 활성화됩니다. PR 요약에서 “Presentation에서는 기간을 옵셔널로 처리”라고 했던 만큼, 실제 요구사항이 “기간 선택은 UI에서만 옵셔널, 제출 시에는 필수”인지 확인이 필요합니다. 만약 제출도 옵셔널 허용이라면 이 조건을 조정해야 합니다.
242-275: RoutineCreationEntity 단일 페이로드 구성으로 일관성 확보반복 요일 재계산, 날짜/시간 포맷 변환, 추천 타입/적용 시점 포함 등 엔티티 구성 로직이 명확하고 도메인/레포/유즈케이스 변경과 잘 맞물립니다.
Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift (1)
211-218: ContentView.Dependency 기반 바인딩이 전반적으로 정확함
- 이름:
namePublisher→nameTextField반영- 세부루틴:
subRoutines의존성/서브타이틀 구성 분리- 반복: 타입별 의존성 구성(.none/.daily/.weekly) 및 표시 텍스트 정렬
- 기간/시간: 옵셔널 값 허용하는 의존성으로 업데이트
새로운 의존성 모델로의 마이그레이션이 일관되게 잘 적용되었습니다.
Also applies to: 222-223, 241-242, 248-249, 256-258, 264-265
| // TODO: - routine 엔티티 변경 이후 시작일자, 종료 일자 설정 필요 + 추천 타입 있으면 추천 타입도 설정 필요 | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
수정 화면에서 기간(시작/종료) 프리필 누락 — 즉시 매핑 추가 필요
루틴 수정 진입 시 시작/종료 일자가 프리필되지 않으면, 현재 유효성 검사(라인 224-231)로 인해 등록/수정 버튼이 비활성화 상태를 유지할 수 있습니다. 실사용에 직접적인 영향이 있으니 이 TODO는 본 PR에서 처리하는 편이 안전합니다.
아래처럼 서버에서 받은 날짜 문자열을 Date로 변환해 subject에 반영해 주세요. 추천 타입도 엔티티에 있다면 같이 세팅하면 좋습니다.
- // TODO: - routine 엔티티 변경 이후 시작일자, 종료 일자 설정 필요 + 추천 타입 있으면 추천 타입도 설정 필요
+ // 시작/종료 일자 프리필
+ let start = Date.convertToDate(from: routine.startDate, dateType: .yearMonthDate)
+ let end = Date.convertToDate(from: routine.endDate, dateType: .yearMonthDate)
+ periodStartSubject.send(start)
+ periodEndSubject.send(end)
+ // 추천 루틴 타입 (존재 시)
+ if let type = routine.recommendedRoutineType {
+ routineType = type
+ }참고: routine.startDate/routine.endDate/recommendedRoutineType 프로퍼티 명은 실제 엔티티 정의에 맞게 조정해 주세요. 필요하시면 해당 부분까지 전체 패치 제안 드리겠습니다.
🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 137-137: TODOs should be resolved (- routine 엔티티 변경 이후 시작일자, 종료 일...)
(todo)
There was a problem hiding this comment.
루틴 엔티티에 해당 프로퍼티들이 새로 추가될 예정입니다. 일주일 내로 pr에 반영하겠습니다.
There was a problem hiding this comment.
@taipaise 이해했습니다! 엔티티 레벨에서 프로퍼티가 추가된 후 매핑 로직을 구현하는 것이 더 적절하겠네요. 일주일 내로 반영 예정이라니 기대하겠습니다. 👍
엔티티 변경 작업 완료되면 해당 부분 리뷰도 도움이 필요하시면 언제든 말씀해 주세요!
choijungp
left a comment
There was a problem hiding this comment.
리뷰 노투 확인했듭니다 ~~~~
수정 버튼 누를때 같이 보내도록 할게용 ~~~~~~ 감사 띵 !!!
짱푸루부 ~~ ☃︎👍🏻
| .sink { [weak self] isRoutineValid in | ||
| if isRoutineValid { | ||
| self?.registerButton.isEnabled = true | ||
| self?.registerButton.backgroundColor = BitnagilColor.gray10 | ||
| self?.registerButton.setTitleColor(.white, for: .normal) | ||
| } else { | ||
| self?.registerButton.isEnabled = false | ||
| self?.registerButton.backgroundColor = BitnagilColor.gray95 | ||
| self?.registerButton.setTitleColor(.white, for: .normal) | ||
| } | ||
| } |
There was a problem hiding this comment.
추후에 PrimaryButton 사용해서 updateButtonState(buttonState: ButtonState)로 처리하면 더 easy 할 듯 합니다 ~~~ 굿쟙 !!
| startDate: routineStartDate, | ||
| endDate: routineEndDate, | ||
| executionTime: executionTime, | ||
| subroutines: subRoutineName, |
There was a problem hiding this comment.
요기서는 왜 subroutines의 r이 소문자인가용 ???
사실 PR 넘 완벽해서 사소한게 보이는 것일 뿐 ~~ 나중 수정두 괜찮슴니다 !!!
There was a problem hiding this comment.
루틴 리스트 작업하실때 일단 이번 작업 내용이 있는게 편하실거 같아서!!! 일단 머지 하구 오늘 따로 pr 올리겠습니다!
🌁 Background
📱 Screenshot
👩💻 Contents
📝 Review Note
updateInfo를nil로 설정하여 생성합니다.Summary by CodeRabbit