diff --git a/Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/HomeAPI.swift b/Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/HomeAPI.swift index 80ff67d1..4e6a9754 100644 --- a/Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/HomeAPI.swift +++ b/Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/HomeAPI.swift @@ -14,7 +14,6 @@ enum HomeAPI { } extension HomeAPI: EndPoint { - var basePath: String { "/api" } @@ -47,7 +46,7 @@ extension HomeAPI: EndPoint { } } - var queryParameters: [String: String]? { + var queryParameters: [String: Any]? { switch self { case .fetchDashboard: return nil diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift index 16f11360..3378bb23 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift @@ -23,7 +23,7 @@ enum CalendarMode { struct CalendarView: View { @EnvironmentObject private var calendarCoordinator: CalendarCoordinator - @ObservedObject var viewModel: CalendarViewModel + @StateObject var viewModel: CalendarViewModel @State private var topGlobalY: CGFloat = .zero @State private var initialTopGlobalY: CGFloat? = nil @State private var bottomOffsetY: CGFloat = .zero @@ -32,20 +32,28 @@ struct CalendarView: View { @State private var buttonState: ButtonState = .active private let scrollAreaHeight: CGFloat = 184.adjustedH + private let calendarCellWidth: CGFloat = 40.adjustedW + private let calendarCellHeight: CGFloat = 40.adjustedH + private let calendarRowSpacing: CGFloat = 8.adjustedH let weekdays: [String] = ["일", "월", "화", "수", "목", "금", "토"] - let columns = Array(repeating: GridItem(.fixed(40), spacing: 8), count: 7) + let columns = Array(repeating: GridItem(.fixed(40.adjustedW), spacing: 8), count: 7) var body: some View { VStack { + Spacer() + .frame(height: 38.adjustedH) + calendarHeader dateGridsView + Spacer() + if viewModel.isEmptyProcedureList() { emptyScheduleView } else { scheduleListContainerView } - + Spacer() } .task (id: viewModel.currentMonth){ if calendarMode == .none { @@ -66,7 +74,7 @@ extension CalendarView { VStack { HStack { Image(.chevronLeft) - .frame(width: 40, height: 40) + .frame(width: 40.adjustedW, height: 40.adjustedH) .scaledToFit() .onTapGesture { viewModel.currentMonth -= 1 @@ -80,30 +88,32 @@ extension CalendarView { Spacer() Image(.chevronRight) - .frame(width: 40, height: 40) + .frame(width: 40.adjustedW, height: 40.adjustedH) .scaledToFit() .onTapGesture { viewModel.currentMonth += 1 viewModel.selectedDate = viewModel.firstDateOfCurrentMonth() } } - .padding(.horizontal, 11) - .padding(.top, 38) - + .padding(.horizontal, 11.adjustedW) HStack(spacing: 8) { ForEach(weekdays, id: \.self) { weekday in TypographyText(weekday, style: .body1_r_14, color: .gray800) - .frame(width: 40, height: 40) + .frame(width: 40.adjustedW, height: 40.adjustedH) } } - .padding(.horizontal, 23) + .padding(.horizontal, 23.adjustedH) } } private var dateGridsView: some View { - LazyVGrid(columns: columns) { - ForEach(viewModel.getDatesArray()) { value in + let dates = viewModel.getDatesArray() + let rowCount = dates.count / 7 + + return VStack(spacing: 0) { + LazyVGrid(columns: columns, spacing: calendarRowSpacing) { + ForEach(dates) { value in if value.day != -1 { CalendarCellView( value: value, @@ -127,11 +137,18 @@ extension CalendarView { } } } else { - Text("").hidden() + Color.clear + .frame(width: calendarCellWidth, height: calendarCellHeight) } } + } + + if rowCount == 4 { + Spacer() + .frame(height: calendarCellHeight + calendarRowSpacing) + } } - .padding(.horizontal, 23) + .padding(.horizontal, 23.adjustedW) } private var scheduleListContainerView: some View { @@ -157,7 +174,7 @@ extension CalendarView { } } .frame(height: 40.adjustedH) - .padding(.horizontal, 20) + .padding(.horizontal, 20.adjustedW) .padding(.top, 8) ZStack { @@ -203,31 +220,31 @@ extension CalendarView { } GradientBox(isTop: true) - .frame(height: 56) + .frame(height: 56.adjustedH) .allowsHitTesting(false) .opacity(shouldShowGradientTop ? 1 : 0) .frame(maxHeight: .infinity, alignment: .top) GradientBox(isTop: false) - .frame(height: 92) + .frame(height: 92.adjustedH) .allowsHitTesting(false) .opacity(shouldShowGradientBottom ? 1 : 0) .frame(maxHeight: .infinity, alignment: .bottom) } .frame(height: scrollAreaHeight.adjustedH) - .padding(.top, 6) - .padding(.horizontal, 19) - .padding(.bottom, 12) + .padding(.top, 6.adjustedW) + .padding(.horizontal, 19.adjustedW) + .padding(.bottom, 12.adjustedH) } .background( RoundedRectangle(cornerRadius: 10) .fill(.gray0) .cherrishShadow() ) - .padding(.top, 20) - .padding(.horizontal, 25) - .padding(.bottom, 18) + .padding(.top, 20.adjustedH) + .padding(.horizontal, 25.adjustedW) + .padding(.bottom, 18.adjustedH) } private var emptyScheduleView: some View { @@ -239,8 +256,8 @@ extension CalendarView { TypographyText("오늘 예정된 일정이 없어요.", style: .body1_r_14, color: .gray600) } - .padding(.top, 50) - .padding(.horizontal, 65) + .padding(.top, 50.adjustedH) + .padding(.horizontal, 65.adjustedW) Spacer() .frame(height: 38.adjustedH) @@ -257,7 +274,7 @@ extension CalendarView { ) } ) - .padding(.horizontal, 24) + .padding(.horizontal, 24.adjustedW) Spacer() .frame(height: 24.adjustedH) @@ -268,9 +285,9 @@ extension CalendarView { .cherrishShadow() ) .frame(width: 326.adjustedW, height: 264.adjustedH) - .padding(.top, 20) - .padding(.horizontal, 25) - .padding(.bottom, 30) + .padding(.top, 20.adjustedH) + .padding(.horizontal, 25.adjustedW) + .padding(.bottom, 30.adjustedH) } private var downTimeRangeIcons: some View { diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift index fdc772cf..71d49b3e 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift @@ -136,21 +136,29 @@ extension CalendarViewModel { } private func extractDate(currentMonth: Int) -> [DateValue] { - let calendar = Calendar.current + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "Asia/Seoul")! - let currentMonth = getCurrentMonth(addingMonth: currentMonth) - var days = currentMonth.getAllDates().compactMap { date -> DateValue in - let day = calendar.component(.day, from: date) - return DateValue(day: day, date: date) - } + let targetMonthDate = getCurrentMonth(addingMonth: currentMonth) + let startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: targetMonthDate))! + let daysInMonth = calendar.range(of: .day, in: .month, for: startOfMonth)?.count ?? 0 - let firstWeekday = calendar.component(.weekday, from: days.first?.date ?? Date()) + let firstWeekday = calendar.component(.weekday, from: startOfMonth) + let leading = firstWeekday - 1 + let totalCells = leading + daysInMonth + let rows = Int(ceil(Double(totalCells) / 7.0)) - for _ in 0 ..< firstWeekday - 1 { - days.insert(DateValue(day: -1, date: Date()), at: 0) - } + let gridStart = calendar.date(byAdding: .day, value: -leading, to: startOfMonth)! - return days + return (0..<(rows * 7)).map { i in + let raw = calendar.date(byAdding: .day, value: i, to: gridStart)! + let date = calendar.startOfDay(for: raw) + + let isInTargetMonth = calendar.isDate(date, equalTo: targetMonthDate, toGranularity: .month) + let day = isInTargetMonth ? calendar.component(.day, from: date) : -1 + + return DateValue(day: day, date: date) + } } private func mapToDowntimeDays(procedure: ProcedureDowntimeEntity) { diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift index 09fbda05..68fb0fa1 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift @@ -79,15 +79,17 @@ private struct HeaderLogoView: View { } private struct ChallengeCardEmptyView: View { + let challengeBarImageName: String private let buttonState: ButtonState = .active + @EnvironmentObject var tabBarCoordinator: TabBarCoordinator var body: some View { VStack(spacing: 0) { HStack(spacing: 0) { TypographyText("챌린지를 시작해봐요!", style: .body1_m_14, color: .gray700) .padding(.top, 18.adjustedH) - + Spacer() } .padding(.leading, 18.adjustedW) @@ -103,7 +105,7 @@ private struct ChallengeCardEmptyView: View { leadingIcon: nil, trailingIcon: nil ) { - + tabBarCoordinator.switchTab(tab: .challenge) } .padding(.horizontal, 24.adjustedW) .padding(.top, 10.adjustedH) @@ -235,7 +237,7 @@ private struct PlanBoxView: View { .cherrishShadow() ) .padding(.horizontal, 24.adjustedW) - + } private var emptyStateView: some View { @@ -251,14 +253,15 @@ private struct PlanBoxView: View { RoundedRectangle(cornerRadius: 10.adjustedW) .strokeBorder(.gray400, lineWidth: 1.adjustedW) ) - .padding(.top, 8.adjustedH) - .padding(.horizontal, 15.adjustedW) + .padding(.top, 8.adjustedH) + .padding(.horizontal, 15.adjustedW) } } private struct UpcomingBoxView: View { @ObservedObject var viewModel: HomeViewModel private let buttonState: ButtonState = .active + @EnvironmentObject var tabBarCoordinator: TabBarCoordinator private func pinStyle(for index: Int, totalCount: Int) -> (circleColor: Color, lineTopColor: Color, lineBottomColor: Color) { let red600 = Color.red600 @@ -275,7 +278,7 @@ private struct UpcomingBoxView: View { case 1: let lineBottomColor = totalCount >= 3 ? red300 : gray0 result = (red500, red500, lineBottomColor) - default: + default: result = (red300, red300, gray0) } @@ -310,9 +313,9 @@ private struct UpcomingBoxView: View { ) .cherrishShadow() .padding(.horizontal, 24.adjustedW) - + } - + private var upcomingListView: some View { VStack(spacing: 0) { ForEach(Array(viewModel.upcomingItems.enumerated()), id: \.element.id) { index, item in @@ -339,8 +342,9 @@ private struct UpcomingBoxView: View { .padding(.top, 11.adjustedH) .padding(.bottom, 12.adjustedH) } - + private var emptyStateView: some View { + VStack(spacing: 0) { Image("illustration_noschedule") .padding(.top, 24.adjustedH) @@ -354,7 +358,7 @@ private struct UpcomingBoxView: View { leadingIcon: nil, trailingIcon: nil ) { - + tabBarCoordinator.switchTab(tab: .calendar) } .padding(.horizontal, 24.adjustedW) .padding(.top, 32.adjustedH)