Skip to content

Commit

Permalink
Merge pull request #88 from YAPP-Github/TNT-195-traineeHomeAPI
Browse files Browse the repository at this point in the history
[TNT-195] ํŠธ๋ ˆ์ด๋‹ˆ ํ™ˆ, ์‹๋‹จ ํ™•์ธ ํ™”๋ฉด API ์—ฐ๊ฒฐ
  • Loading branch information
FpRaArNkK authored Feb 14, 2025
2 parents 2a91d51 + dc796ea commit 6dd9995
Show file tree
Hide file tree
Showing 17 changed files with 391 additions and 29 deletions.
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 @@ -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 }
}),
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public struct TraineeMainFlowFeature {
case .traineeInvitationCodeInput:
state.path.append(.traineeInvitationCodeInput(.init(view_navigationType: .existingUser)))
return .none
case .dietDetailPage(let id):
state.path.append(.dietRecordDetail(.init(dietId: id)))
return .none
}
/// ํŠธ๋ ˆ์ด๋‹ˆ ๋งˆ์ดํŽ˜์ด์ง€
case .traineeMyPage(let screen):
Expand Down Expand Up @@ -147,6 +150,8 @@ extension TraineeMainFlowFeature {
case alarmCheck(AlarmCheckFeature)
/// ์‹๋‹จ ๊ธฐ๋ก ์ถ”๊ฐ€
case addDietRecordPage(TraineeAddDietRecordFeature)
/// ์‹๋‹จ ์ƒ์„ธ ํ™”๋ฉด
case dietRecordDetail(TraineeDietRecordDetailFeature)

// MARK: MyPage
/// ํŠธ๋ ˆ์ด๋‹ˆ ์ดˆ๋Œ€ ์ฝ”๋“œ์ž…๋ ฅ
Expand Down
Loading

0 comments on commit 6dd9995

Please sign in to comment.