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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum HomeAPI {
}

extension HomeAPI: EndPoint {

var basePath: String {
"/api"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// TreatmentInputWarning.swift
// Cherrish-iOS
//
// Created by 어재선 on 1/21/26.
//

import Foundation

enum TreatmentInputWarning {
case none
case pastDate
case invalidFormat
case beforeProcedureDate

var message: String {
switch self {
case .none:
return ""
case .pastDate:
return "이미 지난 날짜는 입력할 수 없어요."
case .invalidFormat:
return "올바른 날짜 형식이 아니에요."
case .beforeProcedureDate:
return "목표일은 시술 날짜 이후로만 설정할 수 있어요."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ struct NoTreatmentView: View {
dDayState: $viewModel.dDay,
year: $viewModel.year,
month: $viewModel.month,
day: $viewModel.day
day: $viewModel.day,
warningMessage: .constant(viewModel.warning.message)
)
.id(String(describing: viewModel.state))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct TargetDdaySettingView: View {
@Binding var year: String
@Binding var month: String
@Binding var day: String

@Binding var warningMessage: String
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack {
Expand Down Expand Up @@ -92,7 +92,8 @@ struct TargetDdaySettingView: View {
.frame(height: 24.adjustedH)

DateTextBox(year: $year, month: $month, day: $day)
.padding(.bottom, 20.adjustedH)
.padding(.bottom, 12.adjustedH)
TreatmentWarningMessgeView(text: warningMessage)
Spacer()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ struct TreatmentView: View {
dDayState: $viewModel.dDay,
year: $viewModel.year,
month: $viewModel.month,
day: $viewModel.day
day: $viewModel.day,
warningMessage: .constant(viewModel.warning.message)
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// TreatmentWarningMessgeView.swift
// Cherrish-iOS
//
// Created by 어재선 on 1/21/26.
//

import SwiftUI

struct TreatmentWarningMessgeView: View {
let text: String
var body: some View {
HStack(spacing: 0) {
TypographyText(text, style: .body1_r_14, color: .red700)
.frame(height: 20.adjustedH)
Spacer()
Comment on lines +10 to +16
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd -t f "TreatmentWarningMessgeView" --exec cat -n {} \;

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 600


🏁 Script executed:

find . -name "*TreatmentWarning*" -type f

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 175


🏁 Script executed:

rg -l "TreatmentWarningMessgeView" --type swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 272


🏁 Script executed:

rg "TreatmentWarningMessgeView" --type swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 524


🏁 Script executed:

rg "body1_r_14" --type swift -A 2 -B 2

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 14796


파일명 및 타입명의 오타 수정이 필요합니다.

TreatmentWarningMessgeView는 오타입니다(Message → Messge). 파일명과 타입명 모두 정정이 필요하며, TargetDdaySettingView.swift의 사용처도 함께 수정해야 합니다.

또한 고정 높이 20.adjustedH는 줄바꿈이나 큰 글꼴 표시 시 텍스트 잘림을 초래할 수 있습니다. 최소 높이로 변경하여 세로 확장을 허용하는 것이 안전합니다.

🛠️ 제안 수정안
-            TypographyText(text, style: .body1_r_14, color: .red700)
-                .frame(height: 20.adjustedH)
+            TypographyText(text, style: .body1_r_14, color: .red700)
+                .fixedSize(horizontal: false, vertical: true)
+                .frame(minHeight: 20.adjustedH, alignment: .leading)
📝 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
struct TreatmentWarningMessgeView: View {
let text: String
var body: some View {
HStack(spacing: 0) {
TypographyText(text, style: .body1_r_14, color: .red700)
.frame(height: 20.adjustedH)
Spacer()
struct TreatmentWarningMessgeView: View {
let text: String
var body: some View {
HStack(spacing: 0) {
TypographyText(text, style: .body1_r_14, color: .red700)
.fixedSize(horizontal: false, vertical: true)
.frame(minHeight: 20.adjustedH, alignment: .leading)
Spacer()
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/TreatmentWarningMessgeView.swift`
around lines 10 - 16, Rename the misspelled view struct and its usages: change
TreatmentWarningMessgeView to TreatmentWarningMessageView (update the type name
and the file name accordingly) and update any references in
TargetDdaySettingView to use the corrected type; also remove the rigid
.frame(height: 20.adjustedH) on TypographyText (or replace it with a flexible
constraint such as .frame(minHeight: <appropriate value>.adjustedH) or rely on
intrinsic size) so the warning text can wrap/expand vertically and avoid
clipping.

}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final class NoTreatmentViewModel: ObservableObject{
@Published var year: String = ""
@Published var month: String = ""
@Published var day: String = ""
@Published private(set) var warning: TreatmentInputWarning = .none

private let fetchCategoriesUseCase: FetchTreatmentCategoriesUseCase
private let fetchTreatmentsUseCase: FetchTreatmentsUseCase
Expand Down Expand Up @@ -113,25 +114,42 @@ final class NoTreatmentViewModel: ObservableObject{

func isDateTextFieldNotEmpty() -> Bool {
guard !year.isEmpty, !month.isEmpty, !day.isEmpty else {
Task { @MainActor in
updateWarning(state: .none)
}
return false
}

guard let yearInt = Int(year), yearInt >= 2020,
let monthInt = Int(month), (1...12).contains(monthInt),
let dayInt = Int(day), (1...31).contains(dayInt) else {
guard let y = Int(year), let m = Int(month), let d = Int(day) else {
updateWarning(state: .invalidFormat)
return false
}

let components = DateComponents(year: yearInt, month: monthInt, day: dayInt)
let components = DateComponents(year: y, month: m, day: d)

guard let date = Calendar.current.date(from: components),
Calendar.current.dateComponents([.year, .month, .day], from: date) == components else {
updateWarning(state: .invalidFormat)
return false
}
Comment on lines +123 to +134
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "NoTreatmentViewModel.swift" | head -5

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 186


🏁 Script executed:

fd "NoTreatmentViewModel.swift" --type f

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 184


🏁 Script executed:

wc -l Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/NoTreatment/NoTreatmentViewModel.swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 188


🏁 Script executed:

sed -n '120,140p' Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/NoTreatment/NoTreatmentViewModel.swift | cat -n

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 936


🌐 Web query:

Swift DateComponents equality comparison calendar timeZone issue

💡 Result:

Short answer

  • DateComponents' synthesized equality compares the components/metadata actually stored in the struct (year/month/day/hour/… plus calendar/timeZone/etc.), so two DateComponents that “represent the same instant” can be unequal if they differ in which fields are set or in calendar/timeZone. [1][2]
  • To compare dates reliably, convert to Date using a known Calendar/timeZone (Calendar.date(from:)) and compare the Date values, or compare only the specific fields you care about after normalizing calendar/timeZone. [3][2]

Why this happens (brief)

  • Calendar.dateComponents(in:from:) often fills many fields (weekday, weekOfYear, yearForWeekOfYear, etc.). If you then mutate one field (e.g. month) the other fields become inconsistent, and equality (or converting back to Date) can give surprising results. The calendar/timeZone stored on DateComponents also affects conversions. [1][2]

What to do (recommended)

  • Normalize before comparing: set the same calendar and timeZone explicitly, and only set/compare the fields you mean (e.g. year/month/day). Or:
    • Best: let a Calendar produce Dates and compare Dates: let d1 = calendar.date(from: dc1); let d2 = calendar.date(from: dc2); compare d1 == d2.
    • Or implement comparison that compares only the subset of fields you need. [3][2]

Sources

  • Swift Forums discussion explaining extra filled fields and inconsistency. [1]
  • Foundation Calendar implementation/docs noting calendar/timeZone precedence and how unspecified fields are assumed. [2]
  • StackOverflow examples recommending converting DateComponents to Date (or normalizing) for reliable comparison. [3]

DateComponents 동등 비교로 정상 입력이 실패할 수 있습니다.

현재 코드의 Calendar.current.dateComponents([.year, .month, .day], from: date) == components 비교는 신뢰할 수 없습니다. DateComponents 동등 비교는 설정된 필드뿐 아니라 calendar, timeZone, 기타 메타데이터를 포함하여 비교하므로, 정상적으로 생성된 날짜도 이 검사에서 실패할 수 있습니다.

제안된 수정안처럼 날짜 생성과 필드 추출을 분리하고 연/월/일 필드를 직접 비교하세요.

🛠️ 제안 수정안
-        let components = DateComponents(year: y, month: m, day: d)
-        guard let date = Calendar.current.date(from: components),
-              Calendar.current.dateComponents([.year, .month, .day], from: date) == components else {
-            updateWarning(state: .invalidFormat)
-            return false
-        }
+        let calendar = Calendar.current
+        let components = DateComponents(year: y, month: m, day: d)
+        guard let date = calendar.date(from: components) else {
+            updateWarning(state: .invalidFormat)
+            return false
+        }
+        let normalized = calendar.dateComponents([.year, .month, .day], from: date)
+        guard normalized.year == y, normalized.month == m, normalized.day == d else {
+            updateWarning(state: .invalidFormat)
+            return false
+        }
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/NoTreatment/NoTreatmentViewModel.swift`
around lines 123 - 134, The current guard in NoTreatmentViewModel that validates
the parsed Ints and constructed DateComponents should not rely on DateComponents
equality; instead after creating date via Calendar.current.date(from:
components) extract let actual = Calendar.current.dateComponents([.year, .month,
.day], from: date) and compare actual.year == y && actual.month == m &&
actual.day == d (keeping the same updateWarning(state: .invalidFormat) return
false flow if the check fails); reference the existing variables year, month,
day, components, date and the updateWarning(state:) call when making this
change.


let today = Calendar.current.startOfDay(for: Date())
if date < today {
updateWarning(state: .pastDate)
return false
}

updateWarning(state: .none)
return true
}


private func updateWarning(state: TreatmentInputWarning) {
Task { @MainActor in
warning = state
}
}

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final class TreatmentViewModel: ObservableObject{
@Published var month: String = ""
@Published var day: String = ""
@Published var searchText = ""
@Published private(set) var warning: TreatmentInputWarning = .none

private let fetchTreatmentsUseCase: FetchTreatmentsUseCase
private let createUserProcedureUseCase: CreateUserProcedureUseCase
Expand Down Expand Up @@ -58,6 +59,8 @@ final class TreatmentViewModel: ObservableObject{
}
}



@MainActor
func fetchTreatments() async throws {
do {
Expand Down Expand Up @@ -98,24 +101,42 @@ final class TreatmentViewModel: ObservableObject{

func isDateTextFieldNotEmpty() -> Bool {
guard !year.isEmpty, !month.isEmpty, !day.isEmpty else {
Task { @MainActor in
updateWarning(state: .none)
}
return false
}

guard let yearInt = Int(year), yearInt >= 2020,
let monthInt = Int(month), (1...12).contains(monthInt),
let dayInt = Int(day), (1...31).contains(dayInt) else {
guard let y = Int(year), let m = Int(month), let d = Int(day) else {
updateWarning(state: .invalidFormat)
return false
}

let components = DateComponents(year: yearInt, month: monthInt, day: dayInt)
let components = DateComponents(year: y, month: m, day: d)

guard let date = Calendar.current.date(from: components),
Calendar.current.dateComponents([.year, .month, .day], from: date) == components else {
updateWarning(state: .invalidFormat)
return false
}
Comment on lines +115 to +121
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Swift DateComponents Equatable equality comparison calendar timeZone

💡 Result:

Short answer

  • DateComponents is just a bag of optional component values plus optional calendar/timeZone metadata. Which equality you get depends on how you compare them:
    • If you compare the raw stored fields (or synthesize Equatable yourself) you are comparing the components and any calendar/timeZone properties directly.
    • If you want to compare the actual moment in time those components represent, you must resolve them to a Date with a Calendar (and that Calendar's timeZone, or the DateComponents.timeZone if set) and compare the resulting Date(s). (nshipster.com)

What that means in practice

  • DateComponents does not magically mean “same instant” when two instances have different combinations of fields, calendars or time zones. Example differences include missing fields, different calendars, or different timeZones producing different Dates when fed to Calendar.date(from:). (nshipster.com)
  • Calendar methods (date(from:), nextDate(matching:), dateComponents(in:from:), etc.) use the Calendar (and its timeZone) to interpret components; if DateComponents.timeZone is set it overrides the calendar’s timeZone for that conversion. Use a Calendar with the desired .timeZone when you need a specific interpretation. (fuchsia.googlesource.com)

Recommended approaches

  • To test “same instant”: convert both to Date using the same Calendar (and timeZone) and compare the Dates.
    • e.g. let d1 = calendar.date(from: dc1); let d2 = calendar.date(from: dc2); then compare d1 == d2. (fuchsia.googlesource.com)
  • To test “same components + same metadata”: compare the stored properties (year/month/day/hour/… plus calendar/timeZone) — either manually or by implementing/synthesizing Equatable for DateComponents in the way you need. (stackoverflow.com)

If you want, I can:

  • show example code for both strategies (raw-field Equatable vs. calendar-resolved Date equality), or
  • help pick the right approach for your use case.

Citations:


🏁 Script executed:

find . -name "TreatmentViewModel.swift" -type f

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 182


🏁 Script executed:

cat -n ./Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift | sed -n '105,130p'

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1094


DateComponents 동등성 비교가 유효한 날짜를 invalid로 처리할 수 있습니다.

라인 115에서 생성한 DateComponents(year: y, month: m, day: d)calendartimeZone이 nil이지만, 라인 118의 Calendar.current.dateComponents(...)로 추출한 DateComponents는 Calendar.current의 calendar와 timeZone이 채워집니다. DateComponents의 == 연산자는 모든 필드(calendar/timeZone 포함)를 비교하므로, 실제로는 유효한 날짜도 비교에 실패하여 .invalidFormat으로 잘못 처리될 수 있습니다.

🔧 제안 수정안
-        guard let date = Calendar.current.date(from: components),
-              Calendar.current.dateComponents([.year, .month, .day], from: date) == components else {
+        guard let date = Calendar.current.date(from: components) else {
+            updateWarning(state: .invalidFormat)
+            return false
+        }
+        let validated = Calendar.current.dateComponents([.year, .month, .day], from: date)
+        guard validated.year == y, validated.month == m, validated.day == d else {
             updateWarning(state: .invalidFormat)
             return false
         }
📝 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
let components = DateComponents(year: y, month: m, day: d)
guard let date = Calendar.current.date(from: components),
Calendar.current.dateComponents([.year, .month, .day], from: date) == components else {
updateWarning(state: .invalidFormat)
return false
}
let components = DateComponents(year: y, month: m, day: d)
guard let date = Calendar.current.date(from: components) else {
updateWarning(state: .invalidFormat)
return false
}
let validated = Calendar.current.dateComponents([.year, .month, .day], from: date)
guard validated.year == y, validated.month == m, validated.day == d else {
updateWarning(state: .invalidFormat)
return false
}
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift`
around lines 115 - 121, The equality check between the constructed
DateComponents (components) and Calendar.current.dateComponents(...) is flawed
because calendar/timeZone differences make == fail for otherwise valid dates; in
TreatmentViewModel.swift replace that equality check by verifying the date was
actually created and/or comparing only the numeric fields or using Calendar
methods: e.g., ensure Calendar.current.date(from: components) returns non-nil
(date != nil) and then either extract year/month/day from
Calendar.current.dateComponents(...) and compare their integer values to y/m/d,
or use Calendar.current.isDate(_:equalTo:toGranularity:) to confirm the same day
before calling updateWarning(state: .invalidFormat).


let today = Calendar.current.startOfDay(for: Date())
if date < today {
updateWarning(state: .pastDate)
return false
}

updateWarning(state: .none)
return true
}


private func updateWarning(state: TreatmentInputWarning) {
Task { @MainActor in
warning = state
}
}

}


Expand Down