Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TNT-195] 트레이니 홈, 식단 확인 화면 API 연결 #88

Merged
merged 7 commits into from
Feb 14, 2025
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 @@ -27,4 +27,16 @@ public struct TraineeRepositoryImpl: TraineeRepository {
public func postTraineeDietRecord(_ reqDTO: PostTraineeDietRecordReqDTO, imgData: Data?) async throws -> PostTraineeDietRecordResDTO {
return try await networkService.request(TraineeTargetType.postTraineeDietRecord(reqDto: reqDTO, imgData: imgData), decodingType: PostTraineeDietRecordResDTO.self)
}

public func getActiveDateList(startDate: String, endDate: String) async throws -> GetActiveDateListResDTO {
return try await networkService.request(TraineeTargetType.getActiveDateList(startDate: startDate, endDate: endDate), decodingType: GetActiveDateListResDTO.self)
}

public func getActiveDateDetail(date: String) async throws -> GetActiveDateDetailResDTO {
return try await networkService.request(TraineeTargetType.getActiveDateDetail(date: date), decodingType: GetActiveDateDetailResDTO.self)
}

public func getDietRecordDetail(dietId: Int) async throws -> GetDietRecordDetailResDTO {
return try await networkService.request(TraineeTargetType.getDietRecordDetail(dietId: dietId), decodingType: GetDietRecordDetailResDTO.self)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public enum TraineeTargetType {
case postConnectTrainer(reqDto: PostConnectTrainerReqDTO)
/// 트레이니 식단 기록 작성
case postTraineeDietRecord(reqDto: PostTraineeDietRecordReqDTO, imgData: Data?)
/// 캘린더 수업, 기록 존재하는 날짜 조회
case getActiveDateList(startDate: String, endDate: String)
/// 특정 날짜 수업, 기록 조회
case getActiveDateDetail(date: String)
/// 특정 식단 조회
case getDietRecordDetail(dietId: Int)
}

extension TraineeTargetType: TargetType {
Expand All @@ -30,20 +36,39 @@ extension TraineeTargetType: TargetType {
return "/connect-trainer"
case .postTraineeDietRecord:
return "/diets"
case .getActiveDateList:
return "/lessons/calendar"
case .getActiveDateDetail(date: let date):
return "/calendar/\(date)"
case .getDietRecordDetail(dietId: let dietId):
return "/diets/\(dietId)"
}
}

var method: HTTPMethod {
switch self {
case .getActiveDateList, .getActiveDateDetail, .getDietRecordDetail:
return .get

case .postConnectTrainer, .postTraineeDietRecord:
return .post
}
}

var task: RequestTask {
switch self {
case .getActiveDateDetail, .getDietRecordDetail:
return .requestPlain

case let .getActiveDateList(startDate, endDate):
return .requestParameters(parameters: [
"startDate": startDate,
"endDate": endDate
], encoding: .url)

case .postConnectTrainer(let reqDto):
return .requestJSONEncodable(encodable: reqDto)

case let .postTraineeDietRecord(reqDto, imgData):
let files: [MultipartFile] = imgData.map {
[.init(fieldName: "dietImage", fileName: "dietImage.png", mimeType: "image/png", data: $0)]
Expand All @@ -59,7 +84,7 @@ extension TraineeTargetType: TargetType {

var headers: [String: String]? {
switch self {
case .postConnectTrainer:
case .postConnectTrainer, .getActiveDateDetail, .getActiveDateList, .getDietRecordDetail:
return ["Content-Type": "application/json"]
case .postTraineeDietRecord:
return ["Content-Type": "multipart/form-data"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public struct TCalendarRepresentable: UIViewRepresentable {
calendar.appearance.titleDefaultColor = .clear
calendar.calendarWeekdayView.weekdayLabels[0].textColor = UIColor(.red500)


return calendar
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ public struct TCalendarView: View {
GeometryReader { proxy in
TCalendarRepresentable(
selectedDate: $selectedDate,
currentPage: $currentPage,
currentPage: Binding(get: {
currentPage
}, set: {
if $0 != currentPage { currentPage = $0 }
}),
Comment on lines +46 to +50
Copy link
Member

Choose a reason for hiding this comment

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

👍🏻

calendarHeight: $calendarHeight,
mode: mode,
events: events
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import SwiftUI

/// 트레이니 - 운동/식단 카드
public struct TRecordCard: View {
private let chipUIInfo: TChip.UIInfo
private let chipUIInfo: TChip.UIInfo?
private let timeText: String
private let title: String
private let imgURL: URL?
private let hasFeedback: Bool
private let footerTapAction: (() -> Void)?

public init(
chipUIInfo: TChip.UIInfo,
chipUIInfo: TChip.UIInfo?,
timeText: String,
title: String,
imgURL: URL?,
Expand Down Expand Up @@ -94,7 +94,9 @@ public struct TRecordCard: View {
@ViewBuilder
public func Header() -> some View {
HStack {
TChip(uiInfo: chipUIInfo)
if let chipUIInfo {
TChip(uiInfo: chipUIInfo)
}
Spacer()
TimeIndicator(timeText: timeText)
}
Expand Down
80 changes: 80 additions & 0 deletions TnT/Projects/Domain/Sources/DTO/Trainee/TraineeResponseDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,83 @@ public struct PostConnectTrainerResDTO: Decodable {

/// 트레이니 식단 기록 응답 DTO
public typealias PostTraineeDietRecordResDTO = EmptyResponse

/// 트레이니 캘린더 수업/기록 존재 날짜 조회 응답 DTO
public struct GetActiveDateListResDTO: Decodable {
public let ptLessonDates: [String]
}

/// 특정 날짜 수업/기록 조회 응답 DTO
public struct GetActiveDateDetailResDTO: Decodable {
public let date: String
public let ptInfo: PTInfoResDTO?
public let diets: [DietResDTO]

enum CodingKeys: String, CodingKey {
case date, ptInfo, diets
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
date = try container.decode(String.self, forKey: .date)
diets = try container.decode([DietResDTO].self, forKey: .diets)

let ptInfoDecoded = try container.decodeIfPresent(PTInfoResDTO.self, forKey: .ptInfo)
ptInfo = ptInfoDecoded?.isEmpty == true ? nil : ptInfoDecoded
}
}

/// PT 정보에 사용되는 PTInfoResDTO
public struct PTInfoResDTO: Decodable {
/// 트레이너 이름
public let trainerName: String?
/// 트레이니 이미지 URL
public let trainerProfileImage: String?
/// 세션 회차
public let session: Int?
/// 수업 시작 시간
public let lessonStart: String?
/// 수업 종료 시간
public let lessonEnd: String?

/// 모든 프로퍼티가 nil인지 확인하는 computed property
public var isEmpty: Bool {
return trainerName == nil &&
trainerProfileImage == nil &&
session == nil &&
lessonStart == nil &&
lessonEnd == nil
}
}

/// 식단 정보에 사용되는 DietResDTO
public struct DietResDTO: Decodable {
/// 식단 ID
public let dietId: Int
/// 식단 시간
public let date: String
/// 식단 이미지 URL
public let dietImageUrl: String?
/// 식단 타입
public let dietType: DietTypeResDTO
/// 식단 메모
public let memo: String
}

/// Breakfast, lunch, dinner, snack으로 구분되는 DietTypeResDTO
public enum DietTypeResDTO: String, Decodable {
case breakfast = "BREAKFAST"
case lunch = "LUNCH"
case dinner = "DINNER"
case snack = "SNACK"
case unknown = ""

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let rawValue = try container.decode(String.self)
self = DietTypeResDTO(rawValue: rawValue) ?? .unknown
}
}

/// 특정 식단 조회 응답 DTO
public typealias GetDietRecordDetailResDTO = DietResDTO
5 changes: 5 additions & 0 deletions TnT/Projects/Domain/Sources/Entity/DietType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public enum DietType: String, Sendable, CaseIterable {
case .snack: return "간식"
}
}

public init?(from recordType: RecordType) {
guard case .diet(let type) = recordType else { return nil }
self = type
}
}
10 changes: 5 additions & 5 deletions TnT/Projects/Domain/Sources/Entity/RecordListItemEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import Foundation

/// 트레이니 기록 목록 아이템 모델
public struct RecordListItemEntity: Equatable {
public struct RecordListItemEntity: Equatable, Sendable {
/// 기록 id
public let id: Int
/// 기록 타입
public let type: RecordType
public let type: RecordType?
/// 기록 시간
public let date: Date
public let date: Date?
/// 기록 제목
public let title: String
/// 피드백 여부
Expand All @@ -25,8 +25,8 @@ public struct RecordListItemEntity: Equatable {

public init(
id: Int,
type: RecordType,
date: Date,
type: RecordType?,
date: Date?,
title: String,
hasFeedBack: Bool,
imageUrl: String?
Expand Down
10 changes: 5 additions & 5 deletions TnT/Projects/Domain/Sources/Entity/WorkoutListItemEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import Foundation

/// 트레이니 PT 운동 목록 아이템 모델
public struct WorkoutListItemEntity: Equatable {
public struct WorkoutListItemEntity: Equatable, Sendable {
/// 수업 Id
public let id: Int
/// 현재 수업 차수
public let currentCount: Int
/// 수업 시작 시간
public let startDate: Date
public let startDate: Date?
/// 수업 종료 시간
public let endDate: Date
public let endDate: Date?
/// 트레이너 프로필 사진 URL
public let trainerProfileImageUrl: String?
/// 트레이너 이름
Expand All @@ -28,8 +28,8 @@ public struct WorkoutListItemEntity: Equatable {
public init(
id: Int,
currentCount: Int,
startDate: Date,
endDate: Date,
startDate: Date?,
endDate: Date?,
trainerProfileImageUrl: String?,
trainerName: String,
hasRecord: Bool
Expand Down
59 changes: 59 additions & 0 deletions TnT/Projects/Domain/Sources/Mapper/TraineeMapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// TraineeMapper.swift
// Domain
//
// Created by 박민서 on 2/14/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation

public extension PTInfoResDTO {
func toEntity() -> WorkoutListItemEntity? {
guard !self.isEmpty else { return nil }
return .init(
id: Int.random(in: 1...10000),
currentCount: self.session ?? 0,
startDate: self.lessonStart?.toDate(format: .ISO8601),
endDate: self.lessonEnd?.toDate(format: .ISO8601),
trainerProfileImageUrl: self.trainerProfileImage,
trainerName: self.trainerName ?? "",
hasRecord: false
)
}
}

public extension DietResDTO {
func toEntity() -> RecordListItemEntity {
return .init(
id: self.dietId,
type: self.dietType.toEntity(),
date: self.date.toDate(format: .ISO8601),
title: self.memo,
hasFeedBack: false,
imageUrl: self.dietImageUrl
)
}
}

public extension DietTypeResDTO {
func toEntity() -> RecordType? {
let dietType: DietType? = {
switch self {
case .breakfast:
return .breakfast
case .lunch:
return .lunch
case .dinner:
return .dinner
case .snack:
return .snack
case .unknown:
return nil
}
}()

guard let dietType else { return nil }
return .diet(type: dietType)
}
}
22 changes: 22 additions & 0 deletions TnT/Projects/Domain/Sources/Repository/TraineeRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,26 @@ public protocol TraineeRepository {
/// - Returns: 등록 성공 시, 응답 DTO (empty) (`PostTraineeDietRecordResDTO`)
/// - Throws: 네트워크 오류 또는 서버에서 반환한 오류를 발생시킬 수 있음
func postTraineeDietRecord(_ reqDTO: PostTraineeDietRecordReqDTO, imgData: Data?) async throws -> PostTraineeDietRecordResDTO

/// 캘린더 수업, 기록 존재하는 날짜 조회 요청
/// - Parameters:
/// - startDate: 조회 시작 날짜
/// - endDate: 조회 종료 날짜
/// - Returns: 등록 성공 시, 응답 DTO (`GetActiveDateListResDTO`)
/// - Throws: 네트워크 오류 또는 서버에서 반환한 오류를 발생시킬 수 있음
func getActiveDateList(startDate: String, endDate: String) async throws -> GetActiveDateListResDTO

/// 특정 날짜 수업, 기록 조회
/// - Parameters:
/// - date: 조회 특정 날짜
/// - Returns: 등록 성공 시, 응답 DTO (`GetActiveDateDetailResDTO`)
/// - Throws: 네트워크 오류 또는 서버에서 반환한 오류를 발생시킬 수 있음
func getActiveDateDetail(date: String) async throws -> GetActiveDateDetailResDTO

/// 특정 식단 조회 요청
/// - Parameters:
/// - dietId: 조회 특정 식단 ID
/// - Returns: 등록 성공 시, 응답 DTO (`GetDietRecordDetailResDTO`)
/// - Throws: 네트워크 오류 또는 서버에서 반환한 오류를 발생시킬 수 있음
func getDietRecordDetail(dietId: Int) async throws -> GetDietRecordDetailResDTO
}
12 changes: 12 additions & 0 deletions TnT/Projects/Domain/Sources/UseCase/TraineeUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,16 @@ extension DefaultTraineeUseCase: TraineeRepository {
public func postTraineeDietRecord(_ reqDTO: PostTraineeDietRecordReqDTO, imgData: Data?) async throws -> PostTraineeDietRecordResDTO {
return try await traineeRepository.postTraineeDietRecord(reqDTO, imgData: imgData)
}

public func getActiveDateList(startDate: String, endDate: String) async throws -> GetActiveDateListResDTO {
return try await traineeRepository.getActiveDateList(startDate: startDate, endDate: endDate)
}

public func getActiveDateDetail(date: String) async throws -> GetActiveDateDetailResDTO {
return try await traineeRepository.getActiveDateDetail(date: date)
}

public func getDietRecordDetail(dietId: Int) async throws -> GetDietRecordDetailResDTO {
return try await traineeRepository.getDietRecordDetail(dietId: dietId)
}
}
Loading