From 8bceb1b543c724b52e21b2d1cf428550e29653cd Mon Sep 17 00:00:00 2001 From: sangyup Date: Thu, 15 Jan 2026 16:42:32 +0900 Subject: [PATCH 01/14] =?UTF-8?q?[Network]=20#184=20-=20=EA=B2=BD=EB=A0=A5?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/Careers/CareersAPI.swift | 7 ++++++ .../Data/Network/Careers/CareersService.swift | 5 ++++ .../DTO/Request/EditCareerRequestDTO.swift | 16 +++++++++++++ .../DefaultCareersRepository.swift | 5 ++++ CERTI-iOS/Domain/Entities/ResumeEntity.swift | 14 +++++++++++ .../Repositories/CareersRepository.swift | 1 + .../UseCases/Careers/EditCareersUseCase.swift | 24 +++++++++++++++++++ 7 files changed, 72 insertions(+) create mode 100644 CERTI-iOS/Data/Network/Careers/DTO/Request/EditCareerRequestDTO.swift create mode 100644 CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift diff --git a/CERTI-iOS/Data/Network/Careers/CareersAPI.swift b/CERTI-iOS/Data/Network/Careers/CareersAPI.swift index 010155a8..ab4e5dd3 100644 --- a/CERTI-iOS/Data/Network/Careers/CareersAPI.swift +++ b/CERTI-iOS/Data/Network/Careers/CareersAPI.swift @@ -13,6 +13,7 @@ enum CareersAPI { case fetchCareersList case deleteCareers(id: Int) case addCareer(request: AddCareerRequestDTO) + case editCareer(request: EditCareerRequestDTO) } extension CareersAPI: BaseTargetType { @@ -31,6 +32,8 @@ extension CareersAPI: BaseTargetType { return "careers/\(id)" case .addCareer: return "careers" + case .editCareer(let careerId): + return "careers/\(careerId)" } } @@ -42,6 +45,8 @@ extension CareersAPI: BaseTargetType { return .delete case .addCareer: return .post + case .editCareer: + return .put } } @@ -53,6 +58,8 @@ extension CareersAPI: BaseTargetType { return .requestPlain case .addCareer(let request): return .requestJSONEncodable(request) + case .editCareer(let request): + return .requestJSONEncodable(request) } } diff --git a/CERTI-iOS/Data/Network/Careers/CareersService.swift b/CERTI-iOS/Data/Network/Careers/CareersService.swift index 7d3c279b..05c4b45d 100644 --- a/CERTI-iOS/Data/Network/Careers/CareersService.swift +++ b/CERTI-iOS/Data/Network/Careers/CareersService.swift @@ -13,6 +13,7 @@ protocol CareersServiceProtocol { func fetchCareersList() async -> Result func deledteCareers(id: Int) async -> Result func addCareer(request: AddCareerRequestDTO) async -> Result + func editCareer(request: EditCareerRequestDTO) async -> Result } final class CareersService: BaseService, CareersServiceProtocol { @@ -29,4 +30,8 @@ final class CareersService: BaseService, CareersServiceProtocol { func addCareer(request: AddCareerRequestDTO) async -> Result { return await requestDecodable(provider, .addCareer(request: request)) } + + func editCareer(request: EditCareerRequestDTO) async -> Result { + return await requestDecodable(provider, .editCareer(request: request)) + } } diff --git a/CERTI-iOS/Data/Network/Careers/DTO/Request/EditCareerRequestDTO.swift b/CERTI-iOS/Data/Network/Careers/DTO/Request/EditCareerRequestDTO.swift new file mode 100644 index 00000000..447c42e2 --- /dev/null +++ b/CERTI-iOS/Data/Network/Careers/DTO/Request/EditCareerRequestDTO.swift @@ -0,0 +1,16 @@ +// +// EditCareerRequestDTO.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/15/26. +// + +import Foundation + +struct EditCareerRequestDTO: Encodable { + let startAt: String + let endAt: String + let place: String + let name: String + let description: String +} diff --git a/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift b/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift index bcad3206..c0275ee4 100644 --- a/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift +++ b/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift @@ -38,4 +38,9 @@ final class DefaultCareersRepository: CareersRepository { let requestDTO = request.toAddCareerRequestDTO() return await service.addCareer(request: requestDTO) } + + func editCareer(request: CareersEntity) async -> Result { + let requestDTO = request.toEditCareerRequestDTO() + return await service.editCareer(request: requestDTO) + } } diff --git a/CERTI-iOS/Domain/Entities/ResumeEntity.swift b/CERTI-iOS/Domain/Entities/ResumeEntity.swift index 8168526a..7e7af833 100644 --- a/CERTI-iOS/Domain/Entities/ResumeEntity.swift +++ b/CERTI-iOS/Domain/Entities/ResumeEntity.swift @@ -42,6 +42,10 @@ struct CareersEntity { func toAddCareerRequestDTO() -> AddCareerRequestDTO { return data.toAddCareerRequestDTO() } + + func toEditCareerRequestDTO() -> EditCareerRequestDTO { + return data.toEditCareerRequestDTO() + } } struct CareersListEntity { @@ -108,4 +112,14 @@ struct ResumeEntityData { description: description ) } + + func toEditCareerRequestDTO() -> EditCareerRequestDTO { + return EditCareerRequestDTO( + startAt: startAt, + endAt: endAt, + place: place, + name: name, + description: description + ) + } } diff --git a/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift b/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift index d7a045f6..401aa463 100644 --- a/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift +++ b/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift @@ -13,4 +13,5 @@ protocol CareersRepository { func fetchCareersList() async -> Result func deleteCareers(id: Int) async -> Result func addCareer(request: CareersEntity) async -> Result + func editCareer(request: CareersEntity) async -> Result } diff --git a/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift b/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift new file mode 100644 index 00000000..25d6b018 --- /dev/null +++ b/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift @@ -0,0 +1,24 @@ +// +// EditCareersUseCase.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/15/26. +// + +import Foundation + +protocol EditCareersUseCase { + func execute(request: CareersEntity) async -> Result +} + +final class DefaultEditCareersUseCase: EditCareersUseCase { + private let repository: CareersRepository + + init(repository: CareersRepository) { + self.repository = repository + } + + func execute(request: CareersEntity) async -> Result { + return await repository.editCareer(request: request) + } +} From bf0c0a1233a2d59131ff5365a39c3ececb770f03 Mon Sep 17 00:00:00 2001 From: sangyup Date: Fri, 16 Jan 2026 13:10:51 +0900 Subject: [PATCH 02/14] =?UTF-8?q?[Chore]=20#184=20-=20MyCareerEditView=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -> MyCareerManageView --- .../{MyCareerEditView.swift => MyCareerManageView.swift} | 8 +++----- .../Presentation/Resume/View/ResumeCoordinatorView.swift | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) rename CERTI-iOS/Presentation/Resume/View/{MyCareerEditView.swift => MyCareerManageView.swift} (94%) diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerEditView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift similarity index 94% rename from CERTI-iOS/Presentation/Resume/View/MyCareerEditView.swift rename to CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift index 38de0cfe..a827132a 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerEditView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift @@ -1,13 +1,13 @@ // -// MyCareerEditView.swift +// MyCareerManageView.swift // CERTI-iOS // -// Created by 이상엽 on 7/12/25. +// Created by 이상엽 on 1/15/26. // import SwiftUI -struct MyCareerEditView: View { +struct MyCareerManageView: View { @ObservedObject var viewModel: ResumeViewModel @State var isDeleteAlertPresented = false @@ -84,10 +84,8 @@ struct MyCareerEditView: View { await viewModel.deleteCareers(id: deleteIndex) } isDeleteAlertPresented = false - print("확인 버튼 클릭") } onCancel: { isDeleteAlertPresented = false - print("취소버튼 클릭") } } } diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift index d52a60ca..04a5f537 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift @@ -47,7 +47,7 @@ struct ResumeCoordinatorView: View { case .myCertificateEdit: MyCertificateEditView(viewModel: resumeViewModel) case .myCareerEdit: - MyCareerEditView(viewModel: resumeViewModel) + MyCareerManageView(viewModel: resumeViewModel) case .myCareerWriteView: MyCareerWriteView(viewModel: resumeViewModel) case .myExtracurricularActivityEditView: From 176304cce1fff82801b0bbac15a000767d7a3e03 Mon Sep 17 00:00:00 2001 From: sangyup Date: Sat, 17 Jan 2026 03:01:26 +0900 Subject: [PATCH 03/14] =?UTF-8?q?[Refactor]=20#184=20-=20Model=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit career / activity --- .../ResumeActivityListComponent.swift | 11 ++-- .../ResumeCareerListComponent.swift | 59 +++++++++++++++++++ .../Resume/Models/ActivityModel.swift | 37 ++++++++++++ .../Resume/Models/ActivityWriteModel.swift | 34 +++++++++++ .../Resume/Models/CareerModel.swift | 19 ++++++ .../Resume/Models/CareerWriteModel.swift | 34 +++++++++++ .../Resume/Models/ResumeListItem.swift | 16 +++++ .../Resume/Models/ResumeModel.swift | 50 ---------------- .../Resume/View/MyCareerManageView.swift | 2 +- .../Resume/View/MyCareerWriteView.swift | 16 ++--- .../MyExtracurricularActivityEditView.swift | 2 +- .../MyExtracurricularActivityWriteView.swift | 17 +++--- .../Presentation/Resume/View/ResumeView.swift | 6 +- .../Resume/ViewModel/ResumeViewModel.swift | 49 +++++++++------ 14 files changed, 256 insertions(+), 96 deletions(-) create mode 100644 CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift create mode 100644 CERTI-iOS/Presentation/Resume/Models/ActivityModel.swift create mode 100644 CERTI-iOS/Presentation/Resume/Models/ActivityWriteModel.swift create mode 100644 CERTI-iOS/Presentation/Resume/Models/CareerModel.swift create mode 100644 CERTI-iOS/Presentation/Resume/Models/CareerWriteModel.swift create mode 100644 CERTI-iOS/Presentation/Resume/Models/ResumeListItem.swift delete mode 100644 CERTI-iOS/Presentation/Resume/Models/ResumeModel.swift diff --git a/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift b/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift index 520e5419..81d0531a 100644 --- a/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift +++ b/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift @@ -8,7 +8,7 @@ import SwiftUI struct ResumeActivityListComponent: View { - let model: ResumeModel + let model: ActivityModel var body: some View { HStack(alignment: .center, spacing: 0) { @@ -48,11 +48,12 @@ struct ResumeActivityListComponent: View { } #Preview { - ResumeActivityListComponent(model: ResumeModel( + ResumeActivityListComponent(model: ActivityModel( + activityId: 1, startAt: "2021.11", endAt: "2022.01", - name: "패션디자이너 인턴", - place: "서티그룹", - description: "트렌드 리서치 및 소재 조사" + name: "sopt", + place: "동아리 36기 기획", + description: "서비스 기획 및 아이디어 도출" )) } diff --git a/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift b/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift new file mode 100644 index 00000000..4a88f767 --- /dev/null +++ b/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift @@ -0,0 +1,59 @@ +// +// ResumeCareerListComponent.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/17/26. +// + +import SwiftUI + +struct ResumeCareerListComponent: View { + let model: CareerModel + + var body: some View { + HStack(alignment: .center, spacing: 0) { + VStack(alignment: .leading, spacing: 0) { + Text("\(model.startAt) ~ \(model.endAt)") + .applyCertiFont(.caption_regular_12) + .foregroundStyle(.grayscale500) + .frame(height: 18) + + Text(model.place) + .applyCertiFont(.caption_regular_12) + .foregroundStyle(.grayscale500) + .frame(height: 18) + .padding(.top, 12) + } + .frame(width: 104, height: 48) + + VStack(alignment: .leading, spacing: 0) { + Text(model.name) + .applyCertiFont(.body_semibold_16) + .foregroundStyle(.grayscale600) + .frame(width: 137, height: 22, alignment: .leading) + + Text(model.description) + .applyCertiFont(.caption_regular_12) + .foregroundStyle(.grayscale600) + .lineLimit(1) + .frame(width: 137, height: 18, alignment: .leading) + .padding(.top, 10) + } + .frame(width: 137) + .padding(.leading, 29) + + Spacer() + } + } +} + +#Preview { + ResumeCareerListComponent(model: CareerModel( + careerId: 1, + startAt: "2021.11", + endAt: "2022.01", + name: "패션디자이너 인턴", + place: "서티그룹", + description: "트렌드 리서치 및 소재 조사" + )) +} diff --git a/CERTI-iOS/Presentation/Resume/Models/ActivityModel.swift b/CERTI-iOS/Presentation/Resume/Models/ActivityModel.swift new file mode 100644 index 00000000..9996af74 --- /dev/null +++ b/CERTI-iOS/Presentation/Resume/Models/ActivityModel.swift @@ -0,0 +1,37 @@ +// +// ActivityModel.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/16/26. +// + +import Foundation + +struct ActivityModel: Identifiable { + var activityId: Int + var startAt: String + var endAt: String + var name: String + var place: String + var description: String + + var id: Int { activityId } +} + +extension ActivityModel { + + + // MARK: - Func + + func toActivityEntity() -> ActivityEntity { + return ActivityEntity( + data: ResumeEntityData( + startAt: startAt, + endAt: endAt, + name: name, + place: place, + description: description + ) + ) + } +} diff --git a/CERTI-iOS/Presentation/Resume/Models/ActivityWriteModel.swift b/CERTI-iOS/Presentation/Resume/Models/ActivityWriteModel.swift new file mode 100644 index 00000000..9f67cf74 --- /dev/null +++ b/CERTI-iOS/Presentation/Resume/Models/ActivityWriteModel.swift @@ -0,0 +1,34 @@ +// +// ActivityWriteModel.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/17/26. +// + +import Foundation + +struct ActivityWriteModel { + var startAt: String = "" + var endAt: String = "" + var name: String = "" + var place: String = "" + var description: String = "" +} + +extension ActivityWriteModel { + + + // MARK: - Func + + func toActivityEntity() -> ActivityEntity { + return ActivityEntity( + data: ResumeEntityData( + startAt: startAt, + endAt: endAt, + name: name, + place: place, + description: description + ) + ) + } +} diff --git a/CERTI-iOS/Presentation/Resume/Models/CareerModel.swift b/CERTI-iOS/Presentation/Resume/Models/CareerModel.swift new file mode 100644 index 00000000..be8cf732 --- /dev/null +++ b/CERTI-iOS/Presentation/Resume/Models/CareerModel.swift @@ -0,0 +1,19 @@ +// +// CareerModel.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/16/26. +// + +import Foundation + +struct CareerModel: Identifiable { + var careerId: Int + var startAt: String + var endAt: String + var name: String + var place: String + var description: String + + var id: Int { careerId } +} diff --git a/CERTI-iOS/Presentation/Resume/Models/CareerWriteModel.swift b/CERTI-iOS/Presentation/Resume/Models/CareerWriteModel.swift new file mode 100644 index 00000000..c1605771 --- /dev/null +++ b/CERTI-iOS/Presentation/Resume/Models/CareerWriteModel.swift @@ -0,0 +1,34 @@ +// +// CareerWriteModel.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/17/26. +// + +import Foundation + +struct CareerWriteModel { + var startAt: String = "" + var endAt: String = "" + var name: String = "" + var place: String = "" + var description: String = "" +} + +extension CareerWriteModel { + + + // MARK: - Func + + func toCareersEntity() -> CareersEntity { + CareersEntity( + data: ResumeEntityData( + startAt: startAt, + endAt: endAt, + name: name, + place: place, + description: description + ) + ) + } +} diff --git a/CERTI-iOS/Presentation/Resume/Models/ResumeListItem.swift b/CERTI-iOS/Presentation/Resume/Models/ResumeListItem.swift new file mode 100644 index 00000000..eea4318f --- /dev/null +++ b/CERTI-iOS/Presentation/Resume/Models/ResumeListItem.swift @@ -0,0 +1,16 @@ +// +// ResumeListItem.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/17/26. +// + +import Foundation + +protocol ResumeListItem { + var startAt: String { get } + var endAt: String { get } + var name: String { get } + var place: String { get } + var description: String { get } +} diff --git a/CERTI-iOS/Presentation/Resume/Models/ResumeModel.swift b/CERTI-iOS/Presentation/Resume/Models/ResumeModel.swift deleted file mode 100644 index f272bff9..00000000 --- a/CERTI-iOS/Presentation/Resume/Models/ResumeModel.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// ResumeModel.swift -// CERTI-iOS -// -// Created by 이상엽 on 7/12/25. -// - -import SwiftUI - -struct ResumeModel: Identifiable { - var id: UUID = UUID() - - var activityId: Int? - var careerId: Int? - var startAt: String - var endAt: String - var name: String - var place: String - var description: String -} - -extension ResumeModel { - - - // MARK: - Func - - func toCareersEntity() -> CareersEntity { - return CareersEntity( - data: ResumeEntityData( - startAt: startAt, - endAt: endAt, - name: name, - place: place, - description: description - ) - ) - } - - func toActivityEntity() -> ActivityEntity { - return ActivityEntity( - data: ResumeEntityData( - startAt: startAt, - endAt: endAt, - name: name, - place: place, - description: description - ) - ) - } -} diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift index a827132a..17ba8bf3 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift @@ -54,7 +54,7 @@ struct MyCareerManageView: View { LazyVGrid(columns: columns, spacing: 36) { ForEach(viewModel.careersList) { item in HStack(alignment: .center, spacing: 0) { - ResumeActivityListComponent(model: item) + ResumeCareerListComponent(model: item) .frame(height: 50) Button { diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift index a4144c99..74481ee7 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift @@ -22,8 +22,8 @@ struct MyCareerWriteView: View { workingPeriodView PeriodInputComponent( isFilled: $viewModel.isPeriodFilled, - startAt: $viewModel.resumeModel.startAt, - endAt: $viewModel.resumeModel.endAt + startAt: $viewModel.careerWriteModel.startAt, + endAt: $viewModel.careerWriteModel.endAt ) workingCompany dutyView @@ -31,17 +31,17 @@ struct MyCareerWriteView: View { ResumeWriteButton( action: { Task { - await viewModel.addCareer(resumeModel: viewModel.resumeModel) + await viewModel.addCareer(careerWriteModel: viewModel.careerWriteModel) viewModel.resumeViewRoutePop() } }, - textEmpty: .constant(viewModel.isWriteButtonEnabled) + textEmpty: .constant(viewModel.isCareerWriteButtonEnabled) ) .padding(.top, 40) } } .onAppear{ - viewModel.clearResumeModel() + viewModel.clearCareerWriteModel() } .scrollIndicators(.hidden) .navigationBarBackButtonHidden() @@ -101,7 +101,7 @@ extension MyCareerWriteView { .padding(.bottom, 24) .padding(.top, 36) - CharLimitTextField(text: $viewModel.resumeModel.name, maxLength: 10) + CharLimitTextField(text: $viewModel.careerWriteModel.name, maxLength: 10) .padding(.horizontal, 20) } } @@ -123,7 +123,7 @@ extension MyCareerWriteView { .padding(.bottom, 24) .padding(.top, 36) - CharLimitTextField(text: $viewModel.resumeModel.place, maxLength: 10) + CharLimitTextField(text: $viewModel.careerWriteModel.place, maxLength: 10) .padding(.horizontal, 20) } } @@ -145,7 +145,7 @@ extension MyCareerWriteView { .padding(.bottom, 24) .padding(.top, 36) - CharLimitTextField(text: $viewModel.resumeModel.description, maxLength: 16) + CharLimitTextField(text: $viewModel.careerWriteModel.description, maxLength: 16) .padding(.horizontal, 20) } } diff --git a/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityEditView.swift b/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityEditView.swift index 00418685..11da6753 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityEditView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityEditView.swift @@ -53,7 +53,7 @@ struct MyExtracurricularActivityEditView: View { .padding(.leading, 20) LazyVGrid(columns: columns, spacing: 36) { - ForEach(viewModel.activityList) { item in + ForEach(viewModel.activitiesList) { item in HStack(alignment: .center, spacing: 0) { ResumeActivityListComponent(model: item) .frame(height: 50) diff --git a/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityWriteView.swift index e7bf150f..92dc9373 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityWriteView.swift @@ -22,8 +22,8 @@ struct MyExtracurricularActivityWriteView: View { activityPeriodView PeriodInputComponent( isFilled: $viewModel.isPeriodFilled, - startAt: $viewModel.resumeModel.startAt, - endAt: $viewModel.resumeModel.endAt + startAt: $viewModel.activityWriteModel.startAt, + endAt: $viewModel.activityWriteModel.endAt ) organizeView activityView @@ -31,18 +31,17 @@ struct MyExtracurricularActivityWriteView: View { ResumeWriteButton( action: { Task { - await viewModel.addActivity(resumeModel: viewModel.resumeModel) - viewModel.clearResumeModel() + await viewModel.addActivity(activityWriteModel: viewModel.activityWriteModel) viewModel.resumeViewRoutePop() } }, - textEmpty: .constant(viewModel.isWriteButtonEnabled) + textEmpty: .constant(viewModel.isActivityWriteButtonEnabled) ) .padding(.top, 40) } } .onAppear{ - viewModel.clearResumeModel() + viewModel.clearActivityWriteModel() } .navigationBarBackButtonHidden() .scrollIndicators(.hidden) @@ -105,7 +104,7 @@ extension MyExtracurricularActivityWriteView { .padding(.bottom, 24) .padding(.top, 36) - CharLimitTextField(text: $viewModel.resumeModel.name, maxLength: 10) + CharLimitTextField(text: $viewModel.activityWriteModel.name, maxLength: 10) .padding(.horizontal, 20) } } @@ -128,7 +127,7 @@ extension MyExtracurricularActivityWriteView { .padding(.bottom, 24) .padding(.top, 36) - CharLimitTextField(text: $viewModel.resumeModel.place, maxLength: 10) + CharLimitTextField(text: $viewModel.activityWriteModel.place, maxLength: 10) .padding(.horizontal, 20) } } @@ -151,7 +150,7 @@ extension MyExtracurricularActivityWriteView { .padding(.bottom, 24) .padding(.top, 36) - CharLimitTextField(text: $viewModel.resumeModel.description, maxLength: 16) + CharLimitTextField(text: $viewModel.activityWriteModel.description, maxLength: 16) .padding(.horizontal, 20) .padding(.bottom, 16) } diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift index b6fe7249..7670d8aa 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift @@ -238,7 +238,7 @@ extension ResumeView { .padding(.top, 20.5) .padding(.bottom, 29.5) - ResumeActivityListComponent(model: item) + ResumeCareerListComponent(model: item) .frame(height: 74) } .padding(.horizontal, 20) @@ -276,7 +276,7 @@ extension ResumeView { private var ResumeMyExtracurricularActivityView: some View { VStack(alignment: .leading, spacing: 0) { - if viewModel.activityList.isEmpty { + if viewModel.activitiesList.isEmpty { VStack(alignment: .center, spacing: 0) { Image(.imageEmpty) .padding(.top, 60) @@ -291,7 +291,7 @@ extension ResumeView { .frame(maxWidth: .infinity) } else { LazyVGrid(columns: columns, spacing: 16) { - ForEach(viewModel.activityList.prefix(4)) { item in + ForEach(viewModel.activitiesList.prefix(4)) { item in HStack(alignment: .center, spacing: 0) { Image(.resumeList) .frame(width: 24, height: 24) diff --git a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift index 34e6a9f1..cf7d6b91 100644 --- a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift +++ b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift @@ -25,20 +25,19 @@ final class ResumeViewModel: ObservableObject { @Published var jobList: [String] = [] @Published var acquisitionList: [CertificatedModel] = [] @Published var acquisitionDetail: CertificatedDetailModel? = nil - @Published var careersList: [ResumeModel] = [] - @Published var activityList: [ResumeModel] = [] + @Published var careersList: [CareerModel] = [] + @Published var activitiesList: [ActivityModel] = [] @Published var isPeriodFilled: Bool = false - @Published var resumeModel = ResumeModel( - startAt: "", - endAt: "", - name: "", - place: "", - description: "" - ) + @Published var careerWriteModel = CareerWriteModel() + @Published var activityWriteModel = ActivityWriteModel() @Published var isCardDetailPresented = false - var isWriteButtonEnabled: Bool { - !resumeModel.name.isBlank && !resumeModel.place.isBlank && !resumeModel.description.isBlank && isPeriodFilled + var isCareerWriteButtonEnabled: Bool { + !careerWriteModel.name.isBlank && !careerWriteModel.place.isBlank && !careerWriteModel.description.isBlank && isPeriodFilled + } + + var isActivityWriteButtonEnabled: Bool { + !activityWriteModel.name.isBlank && !activityWriteModel.place.isBlank && !activityWriteModel.description.isBlank && isPeriodFilled } private let fetchJobUseCase: FetchJobUseCase @@ -79,8 +78,19 @@ final class ResumeViewModel: ObservableObject { self.fetchActivityListUseCase = fetchActivityListUseCase } - func clearResumeModel() { - resumeModel = ResumeModel( + func clearCareerWriteModel() { + careerWriteModel = CareerWriteModel( + startAt: "", + endAt: "", + name: "", + place: "", + description: "" + ) + isPeriodFilled = false + } + + func clearActivityWriteModel() { + activityWriteModel = ActivityWriteModel( startAt: "", endAt: "", name: "", @@ -89,6 +99,7 @@ final class ResumeViewModel: ObservableObject { ) isPeriodFilled = false } + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "CERTI", category: "resume") } @@ -192,8 +203,8 @@ extension ResumeViewModel { } } - func addCareer(resumeModel: ResumeModel) async { - let result = await addCareersUseCase.execute(request: resumeModel.toCareersEntity()) + func addCareer(careerWriteModel: CareerWriteModel) async { + let result = await addCareersUseCase.execute(request: careerWriteModel.toCareersEntity()) switch result { case .success: @@ -222,7 +233,7 @@ extension ResumeViewModel { switch result { case .success(let response): - self.activityList = response.toResumeModel() + self.activitiesList = response.toResumeModel() logger.debug("✅ 대내외활동 조회 성공") case .failure(let error): @@ -237,15 +248,15 @@ extension ResumeViewModel { switch result { case .success(_): logger.info("✅ 대내외 활동 삭제 성공") - activityList.removeAll { $0.activityId == id } + activitiesList.removeAll { $0.activityId == id } case .failure(let error): logger.error("대내외 활동 삭제 failed: \(error.localizedDescription)") } } - func addActivity(resumeModel: ResumeModel) async { - let result = await addActivityUseCase.execute(request: resumeModel.toActivityEntity()) + func addActivity(activityWriteModel: ActivityWriteModel) async { + let result = await addActivityUseCase.execute(request: activityWriteModel.toActivityEntity()) switch result { case .success: From bfd08e79e3b7d7311d6b16955e712e0333331417 Mon Sep 17 00:00:00 2001 From: sangyup Date: Sat, 17 Jan 2026 16:21:04 +0900 Subject: [PATCH 04/14] =?UTF-8?q?[Refactor]=20#184=20-=20Domain=20?= =?UTF-8?q?=EA=B3=84=EC=B8=B5=20Entity=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit career / activity --- .../Domain/Entities/ActivityEntity.swift | 42 ++++++ .../Domain/Entities/ActivityListEntity.swift | 18 +++ CERTI-iOS/Domain/Entities/CareerEntity.swift | 52 ++++++++ .../Domain/Entities/CareerListEntity.swift | 18 +++ CERTI-iOS/Domain/Entities/ResumeEntity.swift | 125 ------------------ 5 files changed, 130 insertions(+), 125 deletions(-) create mode 100644 CERTI-iOS/Domain/Entities/ActivityEntity.swift create mode 100644 CERTI-iOS/Domain/Entities/ActivityListEntity.swift create mode 100644 CERTI-iOS/Domain/Entities/CareerEntity.swift create mode 100644 CERTI-iOS/Domain/Entities/CareerListEntity.swift delete mode 100644 CERTI-iOS/Domain/Entities/ResumeEntity.swift diff --git a/CERTI-iOS/Domain/Entities/ActivityEntity.swift b/CERTI-iOS/Domain/Entities/ActivityEntity.swift new file mode 100644 index 00000000..ef0ba694 --- /dev/null +++ b/CERTI-iOS/Domain/Entities/ActivityEntity.swift @@ -0,0 +1,42 @@ +// +// ActivityEntity.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/17/26. +// + +import Foundation + +struct ActivityEntity { + let activityId: Int + let startAt: String + let endAt: String + let name: String + let place: String + let description: String +} + +// MARK: - Func + +extension ActivityEntity { + func toAddActivityRequestDTO() -> AddActivityRequestDTO { + AddActivityRequestDTO( + startAt: startAt, + endAt: endAt, + place: place, + name: name, + description: description + ) + } + + func toActivityModel() -> ActivityModel { + ActivityModel( + activityId: activityId, + startAt: startAt, + endAt: endAt, + name: name, + place: place, + description: description + ) + } +} diff --git a/CERTI-iOS/Domain/Entities/ActivityListEntity.swift b/CERTI-iOS/Domain/Entities/ActivityListEntity.swift new file mode 100644 index 00000000..c32c710f --- /dev/null +++ b/CERTI-iOS/Domain/Entities/ActivityListEntity.swift @@ -0,0 +1,18 @@ +// +// ActivityListEntity.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/17/26. +// + +import Foundation + +struct ActivityListEntity { + let list: [ActivityEntity] +} + +extension ActivityListEntity { + func toActivityModels() -> [ActivityModel] { + list.map { $0.toActivityModel() } + } +} diff --git a/CERTI-iOS/Domain/Entities/CareerEntity.swift b/CERTI-iOS/Domain/Entities/CareerEntity.swift new file mode 100644 index 00000000..f9cf13f9 --- /dev/null +++ b/CERTI-iOS/Domain/Entities/CareerEntity.swift @@ -0,0 +1,52 @@ +// +// CareerEntity.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/17/26. +// + +import Foundation + +struct CareerEntity { + let careerId: Int + let startAt: String + let endAt: String + let name: String + let place: String + let description: String +} + +// MARK: - Func + +extension CareerEntity { + func toAddCareerRequestDTO() -> AddCareerRequestDTO { + AddCareerRequestDTO( + startAt: startAt, + endAt: endAt, + place: place, + name: name, + description: description + ) + } + + func toEditCareerRequestDTO() -> EditCareerRequestDTO { + EditCareerRequestDTO( + startAt: startAt, + endAt: endAt, + place: place, + name: name, + description: description + ) + } + + func toCareerModel() -> CareerModel { + CareerModel( + careerId: careerId, + startAt: startAt, + endAt: endAt, + name: name, + place: place, + description: description + ) + } +} diff --git a/CERTI-iOS/Domain/Entities/CareerListEntity.swift b/CERTI-iOS/Domain/Entities/CareerListEntity.swift new file mode 100644 index 00000000..bae7059a --- /dev/null +++ b/CERTI-iOS/Domain/Entities/CareerListEntity.swift @@ -0,0 +1,18 @@ +// +// CareerListEntity.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/17/26. +// + +import Foundation + +struct CareerListEntity { + let list: [CareerEntity] +} + +extension CareerListEntity { + func toCareerModels() -> [CareerModel] { + list.map { $0.toCareerModel() } + } +} diff --git a/CERTI-iOS/Domain/Entities/ResumeEntity.swift b/CERTI-iOS/Domain/Entities/ResumeEntity.swift deleted file mode 100644 index 7e7af833..00000000 --- a/CERTI-iOS/Domain/Entities/ResumeEntity.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// ResumeEntity.swift -// CERTI-iOS -// -// Created by 이상엽 on 8/26/25. -// - -import Foundation - -struct ActivityEntity { - let data: ResumeEntityData - - - // MARK: - Func - - func toAddActivityRequestDTO() -> AddActivityRequestDTO { - return data.toAddActivityRequestDTO() - } -} - -struct ActivityListEntity { - let list: [ResumeEntityData] - - - // MARK: - Func - - func toResumeModel() -> [ResumeModel] { - return list.map { $0.toResumeModel() } - } -} - -struct CareersEntity { - let data: ResumeEntityData - - - // MARK: - Func - - func toResumeModel() -> ResumeModel { - return data.toResumeModel() - } - - func toAddCareerRequestDTO() -> AddCareerRequestDTO { - return data.toAddCareerRequestDTO() - } - - func toEditCareerRequestDTO() -> EditCareerRequestDTO { - return data.toEditCareerRequestDTO() - } -} - -struct CareersListEntity { - let list: [ResumeEntityData] - - - // MARK: - Func - - func toResumeModel() -> [ResumeModel] { - return list.map { $0.toResumeModel() } - } -} - -struct ResumeEntityData { - var activityId: Int? - var careerId: Int? - var startAt: String - var endAt: String - var name: String - var place: String - var description: String - - init(activityId: Int? = nil, careerId: Int? = nil, startAt: String, endAt: String, name: String, place: String, description: String) { - self.activityId = activityId - self.careerId = careerId - self.startAt = startAt - self.endAt = endAt - self.name = name - self.place = place - self.description = description - } - - - // MARK: - Func - - func toAddActivityRequestDTO() -> AddActivityRequestDTO { - return AddActivityRequestDTO( - startAt: startAt, - endAt: endAt, - place: place, - name: name, - description: description - ) - } - - func toResumeModel() -> ResumeModel { - return ResumeModel( - activityId: activityId, - careerId: careerId, - startAt: startAt, - endAt: endAt, - name: name, - place: place, - description: description - ) - } - - func toAddCareerRequestDTO() -> AddCareerRequestDTO { - return AddCareerRequestDTO( - startAt: startAt, - endAt: endAt, - place: place, - name: name, - description: description - ) - } - - func toEditCareerRequestDTO() -> EditCareerRequestDTO { - return EditCareerRequestDTO( - startAt: startAt, - endAt: endAt, - place: place, - name: name, - description: description - ) - } -} From 81098bb11c74e574fbf7c94b2a46f1c7df59fc7e Mon Sep 17 00:00:00 2001 From: sangyup Date: Sat, 17 Jan 2026 19:12:54 +0900 Subject: [PATCH 05/14] =?UTF-8?q?[Refactor]=20#184=20-=20Repository,=20Use?= =?UTF-8?q?Case=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Response/ActivityListResponseDTO.swift | 6 +++--- .../DTO/Response/CareersListResponseDTO.swift | 10 ++++----- .../DefaultCareersRepository.swift | 6 +++--- .../Domain/Entities/ActivityEntity.swift | 8 ++++--- .../Domain/Entities/ActivityListEntity.swift | 2 +- CERTI-iOS/Domain/Entities/CareerEntity.swift | 8 ++++--- .../Domain/Entities/CareerListEntity.swift | 2 +- .../Repositories/CareersRepository.swift | 6 +++--- .../UseCases/Careers/AddCareersUseCase.swift | 4 ++-- .../UseCases/Careers/EditCareersUseCase.swift | 4 ++-- .../Careers/FetchCareersListUseCase.swift | 4 ++-- .../ResumePreviewUseCase.swift | 14 ++++++------- .../Resume/Models/ActivityModel.swift | 18 ---------------- .../Resume/Models/ActivityWriteModel.swift | 5 ++--- .../Resume/Models/CareerModel.swift | 16 ++++++++++++++ .../Resume/Models/CareerWriteModel.swift | 21 +++++++++---------- .../Resume/ViewModel/ResumeViewModel.swift | 6 +++--- 17 files changed, 70 insertions(+), 70 deletions(-) diff --git a/CERTI-iOS/Data/Network/Activity/DTO/Response/ActivityListResponseDTO.swift b/CERTI-iOS/Data/Network/Activity/DTO/Response/ActivityListResponseDTO.swift index 03df1ba3..2ba36e21 100644 --- a/CERTI-iOS/Data/Network/Activity/DTO/Response/ActivityListResponseDTO.swift +++ b/CERTI-iOS/Data/Network/Activity/DTO/Response/ActivityListResponseDTO.swift @@ -16,7 +16,7 @@ struct ActivityListData: Decodable { extension ActivityListData { func toActivityListEntity() -> ActivityListEntity { return ActivityListEntity( - list: activityDetailResponses.map{ $0.toResumeEntityData() } + list: activityDetailResponses.map{ $0.toActivityEntity() } ) } } @@ -31,8 +31,8 @@ struct Activity: Decodable, Identifiable { let description: String let place: String - func toResumeEntityData() -> ResumeEntityData { - return ResumeEntityData( + func toActivityEntity() -> ActivityEntity { + return ActivityEntity( activityId: activityId, startAt: startAt, endAt: endAt, diff --git a/CERTI-iOS/Data/Network/Careers/DTO/Response/CareersListResponseDTO.swift b/CERTI-iOS/Data/Network/Careers/DTO/Response/CareersListResponseDTO.swift index 887ebe0c..03e8793d 100644 --- a/CERTI-iOS/Data/Network/Careers/DTO/Response/CareersListResponseDTO.swift +++ b/CERTI-iOS/Data/Network/Careers/DTO/Response/CareersListResponseDTO.swift @@ -14,9 +14,9 @@ struct CareersListData: Decodable { } extension CareersListData { - func toCareersListEntity() -> CareersListEntity { - return CareersListEntity( - list: careerDetailResponseList.map{ $0.toResumeEntityData() } + func toCareersListEntity() -> CareerListEntity { + return CareerListEntity( + list: careerDetailResponseList.map{ $0.toCareerEntity() } ) } } @@ -31,8 +31,8 @@ struct Career: Decodable, Identifiable { let description: String let place: String - func toResumeEntityData() -> ResumeEntityData { - return ResumeEntityData( + func toCareerEntity() -> CareerEntity { + return CareerEntity( careerId: careerId, startAt: startAt, endAt: endAt, diff --git a/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift b/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift index c0275ee4..0f56b851 100644 --- a/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift +++ b/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift @@ -17,7 +17,7 @@ final class DefaultCareersRepository: CareersRepository { self.service = service } - func fetchCareersList() async -> Result { + func fetchCareersList() async -> Result { let result = await service.fetchCareersList() switch result { case .success(let dto): @@ -34,12 +34,12 @@ final class DefaultCareersRepository: CareersRepository { return await service.deledteCareers(id: id) } - func addCareer(request: CareersEntity) async -> Result { + func addCareer(request: CareerEntity) async -> Result { let requestDTO = request.toAddCareerRequestDTO() return await service.addCareer(request: requestDTO) } - func editCareer(request: CareersEntity) async -> Result { + func editCareer(request: CareerEntity) async -> Result { let requestDTO = request.toEditCareerRequestDTO() return await service.editCareer(request: requestDTO) } diff --git a/CERTI-iOS/Domain/Entities/ActivityEntity.swift b/CERTI-iOS/Domain/Entities/ActivityEntity.swift index ef0ba694..7c847a46 100644 --- a/CERTI-iOS/Domain/Entities/ActivityEntity.swift +++ b/CERTI-iOS/Domain/Entities/ActivityEntity.swift @@ -8,7 +8,7 @@ import Foundation struct ActivityEntity { - let activityId: Int + let activityId: Int? let startAt: String let endAt: String let name: String @@ -29,8 +29,10 @@ extension ActivityEntity { ) } - func toActivityModel() -> ActivityModel { - ActivityModel( + func toActivityModel() -> ActivityModel? { + guard let activityId else { return nil } + + return ActivityModel( activityId: activityId, startAt: startAt, endAt: endAt, diff --git a/CERTI-iOS/Domain/Entities/ActivityListEntity.swift b/CERTI-iOS/Domain/Entities/ActivityListEntity.swift index c32c710f..11f8b9f9 100644 --- a/CERTI-iOS/Domain/Entities/ActivityListEntity.swift +++ b/CERTI-iOS/Domain/Entities/ActivityListEntity.swift @@ -13,6 +13,6 @@ struct ActivityListEntity { extension ActivityListEntity { func toActivityModels() -> [ActivityModel] { - list.map { $0.toActivityModel() } + list.compactMap { $0.toActivityModel() } } } diff --git a/CERTI-iOS/Domain/Entities/CareerEntity.swift b/CERTI-iOS/Domain/Entities/CareerEntity.swift index f9cf13f9..ac498fc7 100644 --- a/CERTI-iOS/Domain/Entities/CareerEntity.swift +++ b/CERTI-iOS/Domain/Entities/CareerEntity.swift @@ -8,7 +8,7 @@ import Foundation struct CareerEntity { - let careerId: Int + let careerId: Int? let startAt: String let endAt: String let name: String @@ -39,8 +39,10 @@ extension CareerEntity { ) } - func toCareerModel() -> CareerModel { - CareerModel( + func toCareerModel() -> CareerModel? { + guard let careerId else { return nil } + + return CareerModel( careerId: careerId, startAt: startAt, endAt: endAt, diff --git a/CERTI-iOS/Domain/Entities/CareerListEntity.swift b/CERTI-iOS/Domain/Entities/CareerListEntity.swift index bae7059a..6f5c5e3c 100644 --- a/CERTI-iOS/Domain/Entities/CareerListEntity.swift +++ b/CERTI-iOS/Domain/Entities/CareerListEntity.swift @@ -13,6 +13,6 @@ struct CareerListEntity { extension CareerListEntity { func toCareerModels() -> [CareerModel] { - list.map { $0.toCareerModel() } + list.compactMap { $0.toCareerModel() } } } diff --git a/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift b/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift index 401aa463..7a6cfbde 100644 --- a/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift +++ b/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift @@ -10,8 +10,8 @@ import Foundation import Moya protocol CareersRepository { - func fetchCareersList() async -> Result + func fetchCareersList() async -> Result func deleteCareers(id: Int) async -> Result - func addCareer(request: CareersEntity) async -> Result - func editCareer(request: CareersEntity) async -> Result + func addCareer(request: CareerEntity) async -> Result + func editCareer(request: CareerEntity) async -> Result } diff --git a/CERTI-iOS/Domain/UseCases/Careers/AddCareersUseCase.swift b/CERTI-iOS/Domain/UseCases/Careers/AddCareersUseCase.swift index 10445b63..d0ea2b7a 100644 --- a/CERTI-iOS/Domain/UseCases/Careers/AddCareersUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/Careers/AddCareersUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol AddCareersUseCase { - func execute(request: CareersEntity) async -> Result + func execute(request: CareerEntity) async -> Result } final class DefaultAddCareersUseCase: AddCareersUseCase { @@ -18,7 +18,7 @@ final class DefaultAddCareersUseCase: AddCareersUseCase { self.repository = repository } - func execute(request: CareersEntity) async -> Result { + func execute(request: CareerEntity) async -> Result { return await repository.addCareer(request: request) } } diff --git a/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift b/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift index 25d6b018..5005e79a 100644 --- a/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol EditCareersUseCase { - func execute(request: CareersEntity) async -> Result + func execute(request: CareerEntity) async -> Result } final class DefaultEditCareersUseCase: EditCareersUseCase { @@ -18,7 +18,7 @@ final class DefaultEditCareersUseCase: EditCareersUseCase { self.repository = repository } - func execute(request: CareersEntity) async -> Result { + func execute(request: CareerEntity) async -> Result { return await repository.editCareer(request: request) } } diff --git a/CERTI-iOS/Domain/UseCases/Careers/FetchCareersListUseCase.swift b/CERTI-iOS/Domain/UseCases/Careers/FetchCareersListUseCase.swift index 24b58759..e427d36f 100644 --- a/CERTI-iOS/Domain/UseCases/Careers/FetchCareersListUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/Careers/FetchCareersListUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol FetchCareersListUseCase { - func execute() async -> Result + func execute() async -> Result } final class DefaultFetchCareersListUseCase: FetchCareersListUseCase { @@ -18,7 +18,7 @@ final class DefaultFetchCareersListUseCase: FetchCareersListUseCase { self.repository = repository } - func execute() async -> Result { + func execute() async -> Result { return await repository.fetchCareersList() } } diff --git a/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift b/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift index d2447209..994a166f 100644 --- a/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift @@ -47,25 +47,25 @@ struct PreviewFetchAcquisitionDetailUseCase: FetchAcquisitionDetailUseCase { struct PreviewFetchActivityListUseCase: FetchActivityListUseCase { func execute() async -> Result { let dummyActivities = ActivityListEntity(list:[ - ResumeEntityData(startAt: "2022.03", endAt: "2022.12", name: "SOPT 35기 iOS", place: "SOPT", description: "CERTI 앱 개발 프로젝트 진행"), - ResumeEntityData(startAt: "2023.03", endAt: "2023.07", name: "학교 창업동아리", place: "성균관대", description: "서비스 아이디어 기획 및 발표") + ActivityEntity(activityId: 0, startAt: "2022.03", endAt: "2022.12", name: "SOPT 35기 iOS", place: "SOPT", description: "CERTI 앱 개발 프로젝트 진행"), + ActivityEntity(activityId: 1, startAt: "2023.03", endAt: "2023.07", name: "학교 창업동아리", place: "성균관대", description: "서비스 아이디어 기획 및 발표") ]) return .success(dummyActivities) } } struct PreviewFetchCareersListUseCase: FetchCareersListUseCase { - func execute() async -> Result { - let dummyCareers = CareersListEntity(list:[ - ResumeEntityData(careerId: 1, startAt: "2021.11", endAt: "2022.01", name: "패션디자이너 인턴", place: "서티그룹", description: "트렌드 리서치"), - ResumeEntityData(careerId: 2, startAt: "2023.02", endAt: "2023.07", name: "iOS 개발 인턴", place: "CERTI", description: "CERTI 앱 개발 참여") + func execute() async -> Result { + let dummyCareers = CareerListEntity(list:[ + CareerEntity(careerId: 1, startAt: "2021.11", endAt: "2022.01", name: "패션디자이너 인턴", place: "서티그룹", description: "트렌드 리서치"), + CareerEntity(careerId: 2, startAt: "2023.02", endAt: "2023.07", name: "iOS 개발 인턴", place: "CERTI", description: "CERTI 앱 개발 참여") ]) return .success(dummyCareers) } } struct PreviewAddCareersUseCase: AddCareersUseCase { - func execute(request: CareersEntity) async -> Result { + func execute(request: CareerEntity) async -> Result { .success(true) } } diff --git a/CERTI-iOS/Presentation/Resume/Models/ActivityModel.swift b/CERTI-iOS/Presentation/Resume/Models/ActivityModel.swift index 9996af74..4bc8ca29 100644 --- a/CERTI-iOS/Presentation/Resume/Models/ActivityModel.swift +++ b/CERTI-iOS/Presentation/Resume/Models/ActivityModel.swift @@ -17,21 +17,3 @@ struct ActivityModel: Identifiable { var id: Int { activityId } } - -extension ActivityModel { - - - // MARK: - Func - - func toActivityEntity() -> ActivityEntity { - return ActivityEntity( - data: ResumeEntityData( - startAt: startAt, - endAt: endAt, - name: name, - place: place, - description: description - ) - ) - } -} diff --git a/CERTI-iOS/Presentation/Resume/Models/ActivityWriteModel.swift b/CERTI-iOS/Presentation/Resume/Models/ActivityWriteModel.swift index 9f67cf74..cc65304b 100644 --- a/CERTI-iOS/Presentation/Resume/Models/ActivityWriteModel.swift +++ b/CERTI-iOS/Presentation/Resume/Models/ActivityWriteModel.swift @@ -21,14 +21,13 @@ extension ActivityWriteModel { // MARK: - Func func toActivityEntity() -> ActivityEntity { - return ActivityEntity( - data: ResumeEntityData( + ActivityEntity( + activityId: nil, startAt: startAt, endAt: endAt, name: name, place: place, description: description - ) ) } } diff --git a/CERTI-iOS/Presentation/Resume/Models/CareerModel.swift b/CERTI-iOS/Presentation/Resume/Models/CareerModel.swift index be8cf732..5feb3b95 100644 --- a/CERTI-iOS/Presentation/Resume/Models/CareerModel.swift +++ b/CERTI-iOS/Presentation/Resume/Models/CareerModel.swift @@ -17,3 +17,19 @@ struct CareerModel: Identifiable { var id: Int { careerId } } + + +// MARK: - Func + +extension CareerModel { + func toCareersEntity() -> CareerEntity { + CareerEntity( + careerId: careerId, + startAt: startAt, + endAt: endAt, + name: name, + place: place, + description: description + ) + } +} diff --git a/CERTI-iOS/Presentation/Resume/Models/CareerWriteModel.swift b/CERTI-iOS/Presentation/Resume/Models/CareerWriteModel.swift index c1605771..c72ecea3 100644 --- a/CERTI-iOS/Presentation/Resume/Models/CareerWriteModel.swift +++ b/CERTI-iOS/Presentation/Resume/Models/CareerWriteModel.swift @@ -16,19 +16,18 @@ struct CareerWriteModel { } extension CareerWriteModel { - + // MARK: - Func - - func toCareersEntity() -> CareersEntity { - CareersEntity( - data: ResumeEntityData( - startAt: startAt, - endAt: endAt, - name: name, - place: place, - description: description - ) + + func toCareerEntity() -> CareerEntity { + CareerEntity( + careerId: nil, + startAt: startAt, + endAt: endAt, + name: name, + place: place, + description: description ) } } diff --git a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift index cf7d6b91..c35489d1 100644 --- a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift +++ b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift @@ -195,7 +195,7 @@ extension ResumeViewModel { switch result { case .success(let response): - self.careersList = response.toResumeModel() + self.careersList = response.toCareerModels() logger.debug("✅ 경력사항 조회 성공") case .failure(let error): @@ -204,7 +204,7 @@ extension ResumeViewModel { } func addCareer(careerWriteModel: CareerWriteModel) async { - let result = await addCareersUseCase.execute(request: careerWriteModel.toCareersEntity()) + let result = await addCareersUseCase.execute(request: careerWriteModel.toCareerEntity()) switch result { case .success: @@ -233,7 +233,7 @@ extension ResumeViewModel { switch result { case .success(let response): - self.activitiesList = response.toResumeModel() + self.activitiesList = response.toActivityModels() logger.debug("✅ 대내외활동 조회 성공") case .failure(let error): From 788f5e18c33d383022ee5c479013883f6d8251d7 Mon Sep 17 00:00:00 2001 From: sangyup Date: Sun, 18 Jan 2026 23:57:49 +0900 Subject: [PATCH 06/14] =?UTF-8?q?[Feat]=20#184=20-=20CareerEdit=20/=20Add?= =?UTF-8?q?=20=EB=B7=B0=20=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DIContainer/AppDIContainer.swift | 5 ++ .../Data/Network/Careers/CareersAPI.swift | 6 +- .../Data/Network/Careers/CareersService.swift | 6 +- .../DefaultCareersRepository.swift | 4 +- .../Repositories/CareersRepository.swift | 2 +- .../UseCases/Careers/EditCareersUseCase.swift | 6 +- .../ResumePreviewUseCase.swift | 8 +- .../ResumeCareerListComponent.swift | 8 +- .../Coordinator/ResumeCoordinator.swift | 2 +- .../Resume/Factory/ResumeFactory.swift | 4 + .../Resume/View/MyCareerManageView.swift | 5 +- .../Resume/View/MyCareerWriteView.swift | 71 +++++++++------- .../Resume/View/ResumeCoordinatorView.swift | 8 +- .../Presentation/Resume/View/ResumeView.swift | 5 +- .../Resume/ViewModel/ResumeViewModel.swift | 81 +++++++++++++++---- 15 files changed, 153 insertions(+), 68 deletions(-) diff --git a/CERTI-iOS/Application/DIContainer/AppDIContainer.swift b/CERTI-iOS/Application/DIContainer/AppDIContainer.swift index 95d89c30..f01310b8 100644 --- a/CERTI-iOS/Application/DIContainer/AppDIContainer.swift +++ b/CERTI-iOS/Application/DIContainer/AppDIContainer.swift @@ -147,6 +147,10 @@ extension AppDIContainer { return DefaultFetchCareersListUseCase(repository: careersRepository) } + func makeEditCareerUseCase() -> EditCareersUseCase { + return DefaultEditCareersUseCase(repository: careersRepository) + } + func makeAddActivityUseCase() -> AddActivityUseCase { return DefaultAddActivityUseCase(repository: activityRepository) } @@ -224,6 +228,7 @@ extension AppDIContainer { addCareersUseCase: makeAddCareersUseCase(), deleteCareersUseCase: makeDeleteCareersUseCase(), fetchCareersListUseCase: makeFetchCareersListUseCase(), + editCareerUseCase: makeEditCareerUseCase(), addActivityUseCase: makeAddActivityUseCase(), deleteActivityUseCase: makeDeleteActivityUseCase(), fetchActivityListUseCase: makeFetchActivityListUseCase() diff --git a/CERTI-iOS/Data/Network/Careers/CareersAPI.swift b/CERTI-iOS/Data/Network/Careers/CareersAPI.swift index ab4e5dd3..1c37fe83 100644 --- a/CERTI-iOS/Data/Network/Careers/CareersAPI.swift +++ b/CERTI-iOS/Data/Network/Careers/CareersAPI.swift @@ -13,7 +13,7 @@ enum CareersAPI { case fetchCareersList case deleteCareers(id: Int) case addCareer(request: AddCareerRequestDTO) - case editCareer(request: EditCareerRequestDTO) + case editCareer(careerId:Int, request: EditCareerRequestDTO) } extension CareersAPI: BaseTargetType { @@ -32,7 +32,7 @@ extension CareersAPI: BaseTargetType { return "careers/\(id)" case .addCareer: return "careers" - case .editCareer(let careerId): + case .editCareer(let careerId, _): return "careers/\(careerId)" } } @@ -58,7 +58,7 @@ extension CareersAPI: BaseTargetType { return .requestPlain case .addCareer(let request): return .requestJSONEncodable(request) - case .editCareer(let request): + case .editCareer(_, let request): return .requestJSONEncodable(request) } } diff --git a/CERTI-iOS/Data/Network/Careers/CareersService.swift b/CERTI-iOS/Data/Network/Careers/CareersService.swift index 05c4b45d..6b85787c 100644 --- a/CERTI-iOS/Data/Network/Careers/CareersService.swift +++ b/CERTI-iOS/Data/Network/Careers/CareersService.swift @@ -13,7 +13,7 @@ protocol CareersServiceProtocol { func fetchCareersList() async -> Result func deledteCareers(id: Int) async -> Result func addCareer(request: AddCareerRequestDTO) async -> Result - func editCareer(request: EditCareerRequestDTO) async -> Result + func editCareer(careerId:Int, request: EditCareerRequestDTO) async -> Result } final class CareersService: BaseService, CareersServiceProtocol { @@ -31,7 +31,7 @@ final class CareersService: BaseService, CareersServiceProtocol { return await requestDecodable(provider, .addCareer(request: request)) } - func editCareer(request: EditCareerRequestDTO) async -> Result { - return await requestDecodable(provider, .editCareer(request: request)) + func editCareer(careerId:Int, request: EditCareerRequestDTO) async -> Result { + return await requestDecodable(provider, .editCareer(careerId: careerId, request: request)) } } diff --git a/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift b/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift index 0f56b851..88b3e575 100644 --- a/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift +++ b/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift @@ -39,8 +39,8 @@ final class DefaultCareersRepository: CareersRepository { return await service.addCareer(request: requestDTO) } - func editCareer(request: CareerEntity) async -> Result { + func editCareer(careerId: Int, request: CareerEntity) async -> Result { let requestDTO = request.toEditCareerRequestDTO() - return await service.editCareer(request: requestDTO) + return await service.editCareer(careerId: careerId, request: requestDTO) } } diff --git a/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift b/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift index 7a6cfbde..297c5993 100644 --- a/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift +++ b/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift @@ -13,5 +13,5 @@ protocol CareersRepository { func fetchCareersList() async -> Result func deleteCareers(id: Int) async -> Result func addCareer(request: CareerEntity) async -> Result - func editCareer(request: CareerEntity) async -> Result + func editCareer(careerId:Int, request: CareerEntity) async -> Result } diff --git a/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift b/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift index 5005e79a..40bca1bd 100644 --- a/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol EditCareersUseCase { - func execute(request: CareerEntity) async -> Result + func execute(careerId:Int, request: CareerEntity) async -> Result } final class DefaultEditCareersUseCase: EditCareersUseCase { @@ -18,7 +18,7 @@ final class DefaultEditCareersUseCase: EditCareersUseCase { self.repository = repository } - func execute(request: CareerEntity) async -> Result { - return await repository.editCareer(request: request) + func execute(careerId: Int, request: CareerEntity) async -> Result { + return await repository.editCareer(careerId: careerId, request: request) } } diff --git a/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift b/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift index 994a166f..41d8b583 100644 --- a/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift @@ -70,12 +70,18 @@ struct PreviewAddCareersUseCase: AddCareersUseCase { } } -struct PreviewDeleteCareersUserCase: DeleteCareersUseCase { +struct PreviewDeleteCareersUseCase: DeleteCareersUseCase { func execute(id: Int) async -> Result { return .success(()) } } +struct PreviewEditCareerUseCase: EditCareersUseCase { + func execute(careerId: Int, request: CareerEntity) async -> Result { + .success(true) + } +} + struct PreviewAddActivityUseCase: AddActivityUseCase { func execute(request: ActivityEntity) async -> Result { return .success(()) diff --git a/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift b/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift index 4a88f767..30d3ba35 100644 --- a/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift +++ b/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift @@ -9,7 +9,8 @@ import SwiftUI struct ResumeCareerListComponent: View { let model: CareerModel - + let onTapCard: () -> Void + var body: some View { HStack(alignment: .center, spacing: 0) { VStack(alignment: .leading, spacing: 0) { @@ -44,6 +45,9 @@ struct ResumeCareerListComponent: View { Spacer() } + .onTapGesture { + onTapCard() + } } } @@ -55,5 +59,5 @@ struct ResumeCareerListComponent: View { name: "패션디자이너 인턴", place: "서티그룹", description: "트렌드 리서치 및 소재 조사" - )) + ), onTapCard: {}) } diff --git a/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift b/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift index a1fd3e54..e4750ff8 100644 --- a/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift +++ b/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift @@ -10,7 +10,7 @@ import SwiftUI enum ResumeRoute: Hashable { case myCertificateEdit case myCareerEdit - case myCareerWriteView + case myCareerWriteView(mode: CareerWriteMode) case myExtracurricularActivityEditView case myExtracurricularActivityWriteView } diff --git a/CERTI-iOS/Presentation/Resume/Factory/ResumeFactory.swift b/CERTI-iOS/Presentation/Resume/Factory/ResumeFactory.swift index f79d6280..951e7ba7 100644 --- a/CERTI-iOS/Presentation/Resume/Factory/ResumeFactory.swift +++ b/CERTI-iOS/Presentation/Resume/Factory/ResumeFactory.swift @@ -21,6 +21,7 @@ final class DefaultResumeFactory: ResumeFactory { let addCareersUseCase: AddCareersUseCase let deleteCareersUseCase: DeleteCareersUseCase let fetchCareersListUseCase: FetchCareersListUseCase + let editCareerUseCase: EditCareersUseCase let addActivityUseCase: AddActivityUseCase let deleteActivityUseCase: DeleteActivityUseCase @@ -34,6 +35,7 @@ final class DefaultResumeFactory: ResumeFactory { addCareersUseCase: AddCareersUseCase, deleteCareersUseCase: DeleteCareersUseCase, fetchCareersListUseCase: FetchCareersListUseCase, + editCareerUseCase: EditCareersUseCase, addActivityUseCase: AddActivityUseCase, deleteActivityUseCase: DeleteActivityUseCase, fetchActivityListUseCase: FetchActivityListUseCase @@ -45,6 +47,7 @@ final class DefaultResumeFactory: ResumeFactory { self.addCareersUseCase = addCareersUseCase self.deleteCareersUseCase = deleteCareersUseCase self.fetchCareersListUseCase = fetchCareersListUseCase + self.editCareerUseCase = editCareerUseCase self.addActivityUseCase = addActivityUseCase self.deleteActivityUseCase = deleteActivityUseCase self.fetchActivityListUseCase = fetchActivityListUseCase @@ -60,6 +63,7 @@ final class DefaultResumeFactory: ResumeFactory { addCareersUseCase: addCareersUseCase, deleteCareersUseCase: deleteCareersUseCase, fetchCareersListUseCase: fetchCareersListUseCase, + editCareerUseCase: editCareerUseCase, addActivityUseCase: addActivityUseCase, deleteActivityUseCase: deleteActivityUseCase, fetchActivityListUseCase: fetchActivityListUseCase diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift index 17ba8bf3..b3ddaa25 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift @@ -54,7 +54,10 @@ struct MyCareerManageView: View { LazyVGrid(columns: columns, spacing: 36) { ForEach(viewModel.careersList) { item in HStack(alignment: .center, spacing: 0) { - ResumeCareerListComponent(model: item) + ResumeCareerListComponent(model: item, onTapCard: { + viewModel.selectCareer(id: item.careerId) + viewModel.navigateToCareerEdit2() + }) .frame(height: 50) Button { diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift index 74481ee7..316c9b4f 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift @@ -9,44 +9,55 @@ import SwiftUI struct MyCareerWriteView: View { @ObservedObject var viewModel: ResumeViewModel + let mode: CareerWriteMode var body: some View { - VStack (alignment: .leading, spacing: 0) { - BackButton() { - viewModel.resumeViewRoutePop() - } - - ScrollView { - VStack(alignment: .leading, spacing: 0) { - MyCareerWriteTitleView - workingPeriodView - PeriodInputComponent( - isFilled: $viewModel.isPeriodFilled, - startAt: $viewModel.careerWriteModel.startAt, - endAt: $viewModel.careerWriteModel.endAt - ) - workingCompany - dutyView - dutyDetailView - ResumeWriteButton( - action: { - Task { + VStack (alignment: .leading, spacing: 0) { + BackButton() { + viewModel.resumeViewRoutePop() + } + + ScrollView { + VStack(alignment: .leading, spacing: 0) { + MyCareerWriteTitleView + workingPeriodView + PeriodInputComponent( + isFilled: $viewModel.isPeriodFilled, + startAt: $viewModel.careerWriteModel.startAt, + endAt: $viewModel.careerWriteModel.endAt + ) + workingCompany + dutyView + dutyDetailView + ResumeWriteButton( + action: { + Task { + switch mode { + case .add: await viewModel.addCareer(careerWriteModel: viewModel.careerWriteModel) - viewModel.resumeViewRoutePop() + case .edit(let careerId): + await viewModel.editCareer(careerId: viewModel.selectCareerId, careerWriteModel: viewModel.careerWriteModel) } - }, - textEmpty: .constant(viewModel.isCareerWriteButtonEnabled) - ) - .padding(.top, 40) - } + viewModel.resumeViewRoutePop() + } + }, + textEmpty: .constant(viewModel.isCareerWriteButtonEnabled) + ) + .padding(.top, 40) } - .onAppear{ + } + .onAppear{ + switch mode { + case .add: viewModel.clearCareerWriteModel() + case .edit(let careerId): + viewModel.prepareCareerEdit(careerId: careerId) } - .scrollIndicators(.hidden) - .navigationBarBackButtonHidden() - .scrollDismissesKeyboard(.immediately) } + .scrollIndicators(.hidden) + .navigationBarBackButtonHidden() + .scrollDismissesKeyboard(.immediately) + } } } diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift index 04a5f537..1f8f2813 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift @@ -29,8 +29,8 @@ struct ResumeCoordinatorView: View { switch route { case .navigateToActivityEdit: resumeCoordinator.push(next: .myExtracurricularActivityEditView) - case .navigateToCareerWrite: - resumeCoordinator.push(next: .myCareerWriteView) + case .navigateToCareerWrite(let mode): + resumeCoordinator.push(next: .myCareerWriteView(mode: mode)) case .navigateToActivityWrite: resumeCoordinator.push(next: .myExtracurricularActivityWriteView) case .navigateToCertificatedEdit: @@ -48,8 +48,8 @@ struct ResumeCoordinatorView: View { MyCertificateEditView(viewModel: resumeViewModel) case .myCareerEdit: MyCareerManageView(viewModel: resumeViewModel) - case .myCareerWriteView: - MyCareerWriteView(viewModel: resumeViewModel) + case .myCareerWriteView(let mode): + MyCareerWriteView(viewModel: resumeViewModel, mode: mode) case .myExtracurricularActivityEditView: MyExtracurricularActivityEditView(viewModel: resumeViewModel) case .myExtracurricularActivityWriteView: diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift index 7670d8aa..b8286cb3 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift @@ -238,7 +238,7 @@ extension ResumeView { .padding(.top, 20.5) .padding(.bottom, 29.5) - ResumeCareerListComponent(model: item) + ResumeCareerListComponent(model: item, onTapCard: {}) .frame(height: 74) } .padding(.horizontal, 20) @@ -319,8 +319,9 @@ extension ResumeView { fetchAcquisitionDetailUseCase: PreviewFetchAcquisitionDetailUseCase(), deleteAcquisitionUseCase: PreviewDeleteAcquisitionUseCase(), addCareersUseCase: PreviewAddCareersUseCase(), - deleteCareersUseCase: PreviewDeleteCareersUserCase(), + deleteCareersUseCase: PreviewDeleteCareersUseCase(), fetchCareersListUseCase: PreviewFetchCareersListUseCase(), + editCareerUseCase: PreviewEditCareerUseCase(), addActivityUseCase: PreviewAddActivityUseCase(), deleteActivityUseCase: PreviewDeleteActivityUseCase(), fetchActivityListUseCase: PreviewFetchActivityListUseCase() diff --git a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift index c35489d1..53094144 100644 --- a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift +++ b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift @@ -9,8 +9,8 @@ import Foundation import os -enum ResumeViewRoute { - case navigateToCareerWrite +enum ResumeViewRoute: Equatable { + case navigateToCareerWrite(mode: CareerWriteMode) case navigateToActivityWrite case navigateToCertificatedEdit case navigateToCareerEdit @@ -19,6 +19,11 @@ enum ResumeViewRoute { case resumeViewRoutePop } +enum CareerWriteMode: Hashable { + case add + case edit(careerId: Int) +} + @MainActor final class ResumeViewModel: ObservableObject { @Published var resumeViewRoute: ResumeViewRoute? @@ -31,6 +36,7 @@ final class ResumeViewModel: ObservableObject { @Published var careerWriteModel = CareerWriteModel() @Published var activityWriteModel = ActivityWriteModel() @Published var isCardDetailPresented = false + @Published var selectCareerId: Int = 0 var isCareerWriteButtonEnabled: Bool { !careerWriteModel.name.isBlank && !careerWriteModel.place.isBlank && !careerWriteModel.description.isBlank && isPeriodFilled @@ -49,6 +55,7 @@ final class ResumeViewModel: ObservableObject { private let addCareersUseCase: AddCareersUseCase private let deleteCareersUseCase: DeleteCareersUseCase private let fetchCareersListUseCase: FetchCareersListUseCase + private let editCareerUseCase: EditCareersUseCase private let addActivityUseCase: AddActivityUseCase private let deleteActivityUseCase: DeleteActivityUseCase @@ -62,6 +69,7 @@ final class ResumeViewModel: ObservableObject { addCareersUseCase: AddCareersUseCase, deleteCareersUseCase: DeleteCareersUseCase, fetchCareersListUseCase: FetchCareersListUseCase, + editCareerUseCase: EditCareersUseCase, addActivityUseCase: AddActivityUseCase, deleteActivityUseCase: DeleteActivityUseCase, fetchActivityListUseCase: FetchActivityListUseCase @@ -73,11 +81,12 @@ final class ResumeViewModel: ObservableObject { self.addCareersUseCase = addCareersUseCase self.deleteCareersUseCase = deleteCareersUseCase self.fetchCareersListUseCase = fetchCareersListUseCase + self.editCareerUseCase = editCareerUseCase self.addActivityUseCase = addActivityUseCase self.deleteActivityUseCase = deleteActivityUseCase self.fetchActivityListUseCase = fetchActivityListUseCase } - + func clearCareerWriteModel() { careerWriteModel = CareerWriteModel( startAt: "", @@ -109,13 +118,17 @@ final class ResumeViewModel: ObservableObject { extension ResumeViewModel { func navigateToCareerWrite() { - resumeViewRoute = .navigateToCareerWrite + resumeViewRoute = .navigateToCareerWrite(mode: .add) + } + + func navigateToCareerEdit2() { + resumeViewRoute = .navigateToCareerWrite(mode: .edit(careerId: selectCareerId)) } func navigateToActivityWrite() { resumeViewRoute = .navigateToActivityWrite } - + func navigateToCertificatedEdit() { resumeViewRoute = .navigateToCertificatedEdit } @@ -183,12 +196,12 @@ extension ResumeViewModel { case .success(_): logger.info("✅ 취득한 자격증 삭제 성공") acquisitionList.removeAll { $0.acquisitionId == id } - + case .failure(let error): logger.error("취득한 자격증 삭제 failed: \(error.localizedDescription)") } } - + func getCareersList() async { let result = await fetchCareersListUseCase.execute() @@ -205,7 +218,7 @@ extension ResumeViewModel { func addCareer(careerWriteModel: CareerWriteModel) async { let result = await addCareersUseCase.execute(request: careerWriteModel.toCareerEntity()) - + switch result { case .success: logger.info("✅ 경력 추가 성공") @@ -221,13 +234,24 @@ extension ResumeViewModel { case .success(_): logger.info("✅ 취득한 자격증 삭제 성공") careersList.removeAll { $0.careerId == id } - + case .failure(let error): logger.error("취득한 자격증 삭제 failed: \(error.localizedDescription)") } } - - + + func editCareer(careerId: Int, careerWriteModel: CareerWriteModel) async { + let result = await editCareerUseCase.execute(careerId: careerId, request: careerWriteModel.toCareerEntity()) + + switch result { + case .success(_): + logger.info("✅ 경력 수정 성공") + + case .failure(let error): + logger.error("❌경력 수정 failed: \(error.localizedDescription)") + } + } + func getActivityList() async { let result = await fetchActivityListUseCase.execute() @@ -240,7 +264,7 @@ extension ResumeViewModel { logger.error("❌ 대내외활동 조회 실패: \(error.localizedDescription)") } } - + func deleteActivity(id: Int) async { let result = await deleteActivityUseCase.execute(id: id) @@ -249,15 +273,15 @@ extension ResumeViewModel { case .success(_): logger.info("✅ 대내외 활동 삭제 성공") activitiesList.removeAll { $0.activityId == id } - + case .failure(let error): logger.error("대내외 활동 삭제 failed: \(error.localizedDescription)") } } - + func addActivity(activityWriteModel: ActivityWriteModel) async { let result = await addActivityUseCase.execute(request: activityWriteModel.toActivityEntity()) - + switch result { case .success: logger.info("✅ 활동 추가 성공") @@ -266,3 +290,30 @@ extension ResumeViewModel { } } } + +extension ResumeViewModel { + + + // MARK: - Data Func + + func prepareCareerEdit(careerId: Int) { + guard let career = careersList.first(where: { $0.careerId == careerId }) else { + return + } + + careerWriteModel = CareerWriteModel( + startAt: career.startAt, + endAt: career.endAt, + name: career.name, + place: career.place, + description: career.description + ) + + isPeriodFilled = true + selectCareerId = careerId + } + + func selectCareer(id: Int) { + selectCareerId = id + } +} From 915ec55d40b0dfd1ae7743021f50da80d80409af Mon Sep 17 00:00:00 2001 From: sangyup Date: Sun, 25 Jan 2026 14:52:36 +0900 Subject: [PATCH 07/14] =?UTF-8?q?[Fix]=20#184=20-=20=EA=B2=BD=EB=A0=A5?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8B=9C=20=EB=82=A0=EC=A7=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CERTI-iOS/Global/Extensions/Date+.swift | 31 +++++++++++++++++++ .../Components/PeriodInputComponent.swift | 19 +++++++++--- .../Resume/View/MyCareerWriteView.swift | 4 +-- 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 CERTI-iOS/Global/Extensions/Date+.swift diff --git a/CERTI-iOS/Global/Extensions/Date+.swift b/CERTI-iOS/Global/Extensions/Date+.swift new file mode 100644 index 00000000..311f6ab5 --- /dev/null +++ b/CERTI-iOS/Global/Extensions/Date+.swift @@ -0,0 +1,31 @@ +// +// Date+.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/23/26. +// + +import Foundation + +extension Date { + static func stringToDate(_ value: String) -> Date? { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + + let formats = [ + "yyyy.MM.dd", + + // TODO: - 서버에서 연,월,일 주면 삭제 + "yyyy.MM" + ] + + for format in formats { + formatter.dateFormat = format + if let date = formatter.date(from: value) { + return date + } + } + + return nil + } +} diff --git a/CERTI-iOS/Presentation/Resume/Components/PeriodInputComponent.swift b/CERTI-iOS/Presentation/Resume/Components/PeriodInputComponent.swift index 79950627..91938057 100644 --- a/CERTI-iOS/Presentation/Resume/Components/PeriodInputComponent.swift +++ b/CERTI-iOS/Presentation/Resume/Components/PeriodInputComponent.swift @@ -15,7 +15,7 @@ struct PeriodInputComponent: View { @Binding var isFilled: Bool @Binding var startAt: String @Binding var endAt: String - + var body: some View { VStack(alignment: .leading, spacing: 16) { HStack(alignment: .center, spacing: 0) { @@ -25,14 +25,14 @@ struct PeriodInputComponent: View { placeholder: "시작일" ) .padding(.leading, 20) - + Text("부터") .applyCertiFont(.caption_semibold_14) .foregroundStyle(.grayscale600) .frame(height: 20) .padding(.leading, 8) .padding(.trailing, 10) - + customDatePicker( selectedDate: $endDate, isExpanded: $isEndDateExpanded, @@ -109,6 +109,17 @@ struct PeriodInputComponent: View { .padding(.trailing, 55) } } + .onAppear { + if startDate == nil, !startAt.isEmpty { + startDate = Date.stringToDate(startAt) + } + + if endDate == nil, !endAt.isEmpty { + endDate = Date.stringToDate(endAt) + } + + isFilled = startDate != nil && endDate != nil + } } private var dateFormatter: DateFormatter { @@ -145,7 +156,7 @@ extension PeriodInputComponent { } } label: { HStack(alignment: .center, spacing: 0) { - Text(selectedDate.wrappedValue != nil ? dateFormatter.string(from: selectedDate.wrappedValue!) : placeholder) + Text(selectedDate.wrappedValue != nil ? formatDateToString(selectedDate.wrappedValue!) : placeholder) .applyCertiFont(.caption_semibold_12) .frame(width: 72,height: 18, alignment: .leading) .foregroundColor(selectedDate.wrappedValue != nil ? .grayscale600 : .grayscale300) diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift index 316c9b4f..3ca45894 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift @@ -36,7 +36,7 @@ struct MyCareerWriteView: View { case .add: await viewModel.addCareer(careerWriteModel: viewModel.careerWriteModel) case .edit(let careerId): - await viewModel.editCareer(careerId: viewModel.selectCareerId, careerWriteModel: viewModel.careerWriteModel) + await viewModel.editCareer(careerId: careerId, careerWriteModel: viewModel.careerWriteModel) } viewModel.resumeViewRoutePop() } @@ -65,7 +65,7 @@ extension MyCareerWriteView { private var MyCareerWriteTitleView: some View { Group { HStack(alignment: .center, spacing: 0) { - Text("경력사항 추가") + Text(mode == .add ? "경력사항 추가" : "경력사항 수정") .applyCertiFont(.sub_semibold_20) .foregroundStyle(.grayscale600) .frame(height: 26) From cd386b92f46712d228c92a1eaae543b2a58ad3b5 Mon Sep 17 00:00:00 2001 From: sangyup Date: Mon, 26 Jan 2026 13:25:56 +0900 Subject: [PATCH 08/14] =?UTF-8?q?[Chore]=20#184=20-=20Activity,=20EditView?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -> ManageView --- .../Coordinator/ResumeCoordinator.swift | 6 +++--- ...tView.swift => MyActivityManageView.swift} | 4 ++-- ...teView.swift => MyActivityWriteView.swift} | 6 +++--- .../Resume/View/ResumeCoordinatorView.swift | 20 +++++++++---------- .../Presentation/Resume/View/ResumeView.swift | 4 ++-- .../Resume/ViewModel/ResumeViewModel.swift | 12 +++++------ 6 files changed, 26 insertions(+), 26 deletions(-) rename CERTI-iOS/Presentation/Resume/View/{MyExtracurricularActivityEditView.swift => MyActivityManageView.swift} (97%) rename CERTI-iOS/Presentation/Resume/View/{MyExtracurricularActivityWriteView.swift => MyActivityWriteView.swift} (97%) diff --git a/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift b/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift index e4750ff8..b73f2d66 100644 --- a/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift +++ b/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift @@ -9,10 +9,10 @@ import SwiftUI enum ResumeRoute: Hashable { case myCertificateEdit - case myCareerEdit + case myCareerManageView case myCareerWriteView(mode: CareerWriteMode) - case myExtracurricularActivityEditView - case myExtracurricularActivityWriteView + case myActivityManageView + case myActivityWriteView } final class ResumeCoordinator: ObservableObject { diff --git a/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityEditView.swift b/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift similarity index 97% rename from CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityEditView.swift rename to CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift index 11da6753..386c23eb 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityEditView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift @@ -1,5 +1,5 @@ // -// MyExtracurricularActivityEditView.swift +// MyActivityManageView.swift // CERTI-iOS // // Created by 이상엽 on 7/13/25. @@ -7,7 +7,7 @@ import SwiftUI -struct MyExtracurricularActivityEditView: View { +struct MyActivityManageView: View { @ObservedObject var viewModel: ResumeViewModel @State var isDeleteAlertPresented = false @State var selectedActivityIndex : Int? = nil diff --git a/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift similarity index 97% rename from CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityWriteView.swift rename to CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift index 92dc9373..e420efb5 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyExtracurricularActivityWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift @@ -1,5 +1,5 @@ // -// MyExtracurricularActivityWriteView.swift +// MyActivityWriteView.swift // CERTI-iOS // // Created by 이상엽 on 7/13/25. @@ -7,7 +7,7 @@ import SwiftUI -struct MyExtracurricularActivityWriteView: View { +struct MyActivityWriteView: View { @ObservedObject var viewModel: ResumeViewModel var body: some View { @@ -50,7 +50,7 @@ struct MyExtracurricularActivityWriteView: View { } } -extension MyExtracurricularActivityWriteView { +extension MyActivityWriteView { private var MyExtracurricularActivityTitleView: some View { Group { HStack(alignment: .center, spacing: 0) { diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift index 1f8f2813..f85ca8ee 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift @@ -27,16 +27,16 @@ struct ResumeCoordinatorView: View { .onChange(of: resumeViewModel.resumeViewRoute) { route in guard let route = route else { return } switch route { - case .navigateToActivityEdit: - resumeCoordinator.push(next: .myExtracurricularActivityEditView) + case .navigateToActivityManage: + resumeCoordinator.push(next: .myActivityManageView) case .navigateToCareerWrite(let mode): resumeCoordinator.push(next: .myCareerWriteView(mode: mode)) case .navigateToActivityWrite: - resumeCoordinator.push(next: .myExtracurricularActivityWriteView) + resumeCoordinator.push(next: .myActivityWriteView) case .navigateToCertificatedEdit: resumeCoordinator.push(next: .myCertificateEdit) - case .navigateToCareerEdit: - resumeCoordinator.push(next: .myCareerEdit) + case .navigateToCareerManage: + resumeCoordinator.push(next: .myCareerManageView) case .resumeViewRoutePop: resumeCoordinator.pop() } @@ -46,14 +46,14 @@ struct ResumeCoordinatorView: View { switch route { case .myCertificateEdit: MyCertificateEditView(viewModel: resumeViewModel) - case .myCareerEdit: + case .myCareerManageView: MyCareerManageView(viewModel: resumeViewModel) case .myCareerWriteView(let mode): MyCareerWriteView(viewModel: resumeViewModel, mode: mode) - case .myExtracurricularActivityEditView: - MyExtracurricularActivityEditView(viewModel: resumeViewModel) - case .myExtracurricularActivityWriteView: - MyExtracurricularActivityWriteView(viewModel: resumeViewModel) + case .myActivityManageView: + MyActivityManageView(viewModel: resumeViewModel) + case .myActivityWriteView: + MyActivityWriteView(viewModel: resumeViewModel) } } } diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift index b8286cb3..30291d3f 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift @@ -203,7 +203,7 @@ extension ResumeView { Spacer() Button { - viewModel.navigateToCareerEdit() + viewModel.navigateToCareerManage() } label: { Image(.iconArrowright36) } @@ -264,7 +264,7 @@ extension ResumeView { Spacer() Button { - viewModel.navigateToActivityEdit() + viewModel.navigateToActivityManage() } label: { Image(.iconArrowright36) } diff --git a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift index 53094144..bb934d11 100644 --- a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift +++ b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift @@ -13,8 +13,8 @@ enum ResumeViewRoute: Equatable { case navigateToCareerWrite(mode: CareerWriteMode) case navigateToActivityWrite case navigateToCertificatedEdit - case navigateToCareerEdit - case navigateToActivityEdit + case navigateToCareerManage + case navigateToActivityManage case resumeViewRoutePop } @@ -133,12 +133,12 @@ extension ResumeViewModel { resumeViewRoute = .navigateToCertificatedEdit } - func navigateToCareerEdit() { - resumeViewRoute = .navigateToCareerEdit + func navigateToCareerManage() { + resumeViewRoute = .navigateToCareerManage } - func navigateToActivityEdit() { - resumeViewRoute = .navigateToActivityEdit + func navigateToActivityManage() { + resumeViewRoute = .navigateToActivityManage } func resumeViewRoutePop() { From 4de7625de4cff2230f974cc200fbfec7f4e49da4 Mon Sep 17 00:00:00 2001 From: sangyup Date: Mon, 26 Jan 2026 14:01:18 +0900 Subject: [PATCH 09/14] =?UTF-8?q?[Network]=20#184=20-=20=EB=8C=80=EB=82=B4?= =?UTF-8?q?=EC=99=B8=ED=99=9C=EB=8F=99=20=EC=88=98=EC=A0=95=20API=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DIContainer/AppDIContainer.swift | 7 ++++- .../Data/Network/Activity/ActivityAPI.swift | 7 +++++ .../Network/Activity/ActivityService.swift | 5 ++++ .../DTO/Request/EditActivityRequestDTO.swift | 16 +++++++++++ .../DefaultActivityRepository.swift | 5 ++++ .../Domain/Entities/ActivityEntity.swift | 10 +++++++ .../Repositories/ActivityRepository.swift | 1 + .../Activity/EditActivityUseCase.swift | 24 +++++++++++++++++ .../Coordinator/ResumeCoordinator.swift | 2 +- .../Resume/Factory/ResumeFactory.swift | 8 ++++-- .../Resume/View/MyActivityWriteView.swift | 11 ++++++-- .../Resume/View/ResumeCoordinatorView.swift | 8 +++--- .../Presentation/Resume/View/ResumeView.swift | 18 ------------- .../Resume/ViewModel/ResumeViewModel.swift | 27 ++++++++++++++++--- 14 files changed, 118 insertions(+), 31 deletions(-) create mode 100644 CERTI-iOS/Data/Network/Activity/DTO/Request/EditActivityRequestDTO.swift create mode 100644 CERTI-iOS/Domain/UseCases/Activity/EditActivityUseCase.swift diff --git a/CERTI-iOS/Application/DIContainer/AppDIContainer.swift b/CERTI-iOS/Application/DIContainer/AppDIContainer.swift index f01310b8..776c7679 100644 --- a/CERTI-iOS/Application/DIContainer/AppDIContainer.swift +++ b/CERTI-iOS/Application/DIContainer/AppDIContainer.swift @@ -159,6 +159,10 @@ extension AppDIContainer { return DefaultDeleteActivityUseCase(repository: activityRepository) } + func makeEditActivityUseCase() -> EditActivityUseCase { + return DefaultEditActivityUseCase(repository: activityRepository) + } + func makeFetchActivityListUseCase() -> FetchActivityListUseCase { return DefaultFetchActivityListUseCase(repository: activityRepository) } @@ -231,7 +235,8 @@ extension AppDIContainer { editCareerUseCase: makeEditCareerUseCase(), addActivityUseCase: makeAddActivityUseCase(), deleteActivityUseCase: makeDeleteActivityUseCase(), - fetchActivityListUseCase: makeFetchActivityListUseCase() + fetchActivityListUseCase: makeFetchActivityListUseCase(), + editActivityUseCase: makeEditActivityUseCase() ) } diff --git a/CERTI-iOS/Data/Network/Activity/ActivityAPI.swift b/CERTI-iOS/Data/Network/Activity/ActivityAPI.swift index 645999c0..92a49ee0 100644 --- a/CERTI-iOS/Data/Network/Activity/ActivityAPI.swift +++ b/CERTI-iOS/Data/Network/Activity/ActivityAPI.swift @@ -13,6 +13,7 @@ enum ActivityAPI { case fetchActivityList case addActivity(request: AddActivityRequestDTO) case deleteActivity(id: Int) + case editActivity(activityId:Int, request: EditActivityRequestDTO) } extension ActivityAPI: BaseTargetType { @@ -31,6 +32,8 @@ extension ActivityAPI: BaseTargetType { return "activity" case .deleteActivity(let id): return "activity/\(id)" + case .editActivity(let activityId, _): + return "activity/\(activityId)" } } @@ -42,6 +45,8 @@ extension ActivityAPI: BaseTargetType { return .post case .deleteActivity: return .delete + case .editActivity: + return .put } } @@ -53,6 +58,8 @@ extension ActivityAPI: BaseTargetType { return .requestJSONEncodable(request) case .deleteActivity: return .requestPlain + case .editActivity(_, let request): + return .requestJSONEncodable(request) } } diff --git a/CERTI-iOS/Data/Network/Activity/ActivityService.swift b/CERTI-iOS/Data/Network/Activity/ActivityService.swift index 96d9da5f..a141e51e 100644 --- a/CERTI-iOS/Data/Network/Activity/ActivityService.swift +++ b/CERTI-iOS/Data/Network/Activity/ActivityService.swift @@ -13,6 +13,7 @@ protocol ActivityServiceProtocol { func fetchActivityList() async -> Result func deleteActivity(id: Int) async -> Result func addActivity(request: AddActivityRequestDTO) async -> Result + func editActivity(activityId: Int, request: EditActivityRequestDTO) async -> Result } final class ActivityService: BaseService, ActivityServiceProtocol { @@ -29,4 +30,8 @@ final class ActivityService: BaseService, ActivityServiceProtocol { func addActivity(request: AddActivityRequestDTO) async -> Result { return await requestVoid(provider, .addActivity(request: request)) } + + func editActivity(activityId: Int, request: EditActivityRequestDTO) async -> Result { + return await requestVoid(provider, .editActivity(activityId: activityId, request: request)) + } } diff --git a/CERTI-iOS/Data/Network/Activity/DTO/Request/EditActivityRequestDTO.swift b/CERTI-iOS/Data/Network/Activity/DTO/Request/EditActivityRequestDTO.swift new file mode 100644 index 00000000..fda28f50 --- /dev/null +++ b/CERTI-iOS/Data/Network/Activity/DTO/Request/EditActivityRequestDTO.swift @@ -0,0 +1,16 @@ +// +// EditActivityRequestDTO.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/26/26. +// + +import Foundation + +struct EditActivityRequestDTO: Encodable { + let startAt: String + let endAt: String + let place: String + let name: String + let description: String +} diff --git a/CERTI-iOS/Data/Repositories/DefaultActivityRepository.swift b/CERTI-iOS/Data/Repositories/DefaultActivityRepository.swift index 8e36cc6e..4b46ba49 100644 --- a/CERTI-iOS/Data/Repositories/DefaultActivityRepository.swift +++ b/CERTI-iOS/Data/Repositories/DefaultActivityRepository.swift @@ -38,4 +38,9 @@ final class DefaultActivityRepository: ActivityRepository { let requestDTO = request.toAddActivityRequestDTO() return await service.addActivity(request: requestDTO) } + + func editActivity(activityId: Int, request: ActivityEntity) async -> Result { + let requestDTO = request.toEditActivityRequestDTO() + return await service.editActivity(activityId: activityId, request: requestDTO) + } } diff --git a/CERTI-iOS/Domain/Entities/ActivityEntity.swift b/CERTI-iOS/Domain/Entities/ActivityEntity.swift index 7c847a46..34055b66 100644 --- a/CERTI-iOS/Domain/Entities/ActivityEntity.swift +++ b/CERTI-iOS/Domain/Entities/ActivityEntity.swift @@ -29,6 +29,16 @@ extension ActivityEntity { ) } + func toEditActivityRequestDTO() -> EditActivityRequestDTO { + EditActivityRequestDTO( + startAt: startAt, + endAt: endAt, + place: place, + name: name, + description: description + ) + } + func toActivityModel() -> ActivityModel? { guard let activityId else { return nil } diff --git a/CERTI-iOS/Domain/Interfaces/Repositories/ActivityRepository.swift b/CERTI-iOS/Domain/Interfaces/Repositories/ActivityRepository.swift index 15259a90..fa43d744 100644 --- a/CERTI-iOS/Domain/Interfaces/Repositories/ActivityRepository.swift +++ b/CERTI-iOS/Domain/Interfaces/Repositories/ActivityRepository.swift @@ -13,4 +13,5 @@ protocol ActivityRepository { func fetchActivityList() async -> Result func deleteActivity(id: Int) async -> Result func addActivity(request: ActivityEntity) async -> Result + func editActivity(activityId: Int, request: ActivityEntity) async -> Result } diff --git a/CERTI-iOS/Domain/UseCases/Activity/EditActivityUseCase.swift b/CERTI-iOS/Domain/UseCases/Activity/EditActivityUseCase.swift new file mode 100644 index 00000000..89f659c6 --- /dev/null +++ b/CERTI-iOS/Domain/UseCases/Activity/EditActivityUseCase.swift @@ -0,0 +1,24 @@ +// +// EditActivityUseCase.swift +// CERTI-iOS +// +// Created by 이상엽 on 1/26/26. +// + +import Foundation + +protocol EditActivityUseCase { + func execute(activityId:Int, request: ActivityEntity) async -> Result +} + +final class DefaultEditActivityUseCase: EditActivityUseCase { + private let repository: ActivityRepository + + init(repository: ActivityRepository) { + self.repository = repository + } + + func execute(activityId: Int, request: ActivityEntity) async -> Result { + return await repository.editActivity(activityId: activityId, request: request) + } +} diff --git a/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift b/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift index b73f2d66..c143848c 100644 --- a/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift +++ b/CERTI-iOS/Presentation/Resume/Coordinator/ResumeCoordinator.swift @@ -12,7 +12,7 @@ enum ResumeRoute: Hashable { case myCareerManageView case myCareerWriteView(mode: CareerWriteMode) case myActivityManageView - case myActivityWriteView + case myActivityWriteView(mode: ActivityWriteMode) } final class ResumeCoordinator: ObservableObject { diff --git a/CERTI-iOS/Presentation/Resume/Factory/ResumeFactory.swift b/CERTI-iOS/Presentation/Resume/Factory/ResumeFactory.swift index 951e7ba7..3d65302a 100644 --- a/CERTI-iOS/Presentation/Resume/Factory/ResumeFactory.swift +++ b/CERTI-iOS/Presentation/Resume/Factory/ResumeFactory.swift @@ -26,6 +26,7 @@ final class DefaultResumeFactory: ResumeFactory { let addActivityUseCase: AddActivityUseCase let deleteActivityUseCase: DeleteActivityUseCase let fetchActivityListUseCase: FetchActivityListUseCase + let editActivityUseCase: EditActivityUseCase init( fetchJobUseCase: FetchJobUseCase, @@ -38,7 +39,8 @@ final class DefaultResumeFactory: ResumeFactory { editCareerUseCase: EditCareersUseCase, addActivityUseCase: AddActivityUseCase, deleteActivityUseCase: DeleteActivityUseCase, - fetchActivityListUseCase: FetchActivityListUseCase + fetchActivityListUseCase: FetchActivityListUseCase, + editActivityUseCase: EditActivityUseCase ) { self.fetchJobUseCase = fetchJobUseCase self.fetchAcquisitionListUseCase = fetchAcquisitionListUseCase @@ -51,6 +53,7 @@ final class DefaultResumeFactory: ResumeFactory { self.addActivityUseCase = addActivityUseCase self.deleteActivityUseCase = deleteActivityUseCase self.fetchActivityListUseCase = fetchActivityListUseCase + self.editActivityUseCase = editActivityUseCase } @MainActor @@ -66,7 +69,8 @@ final class DefaultResumeFactory: ResumeFactory { editCareerUseCase: editCareerUseCase, addActivityUseCase: addActivityUseCase, deleteActivityUseCase: deleteActivityUseCase, - fetchActivityListUseCase: fetchActivityListUseCase + fetchActivityListUseCase: fetchActivityListUseCase, + editActivityUseCase: editActivityUseCase ) } } diff --git a/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift index e420efb5..354152e0 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift @@ -9,7 +9,8 @@ import SwiftUI struct MyActivityWriteView: View { @ObservedObject var viewModel: ResumeViewModel - + let mode: ActivityWriteMode + var body: some View { VStack (alignment: .leading, spacing: 0) { BackButton() { @@ -30,8 +31,14 @@ struct MyActivityWriteView: View { activityDetailView ResumeWriteButton( action: { + Task { - await viewModel.addActivity(activityWriteModel: viewModel.activityWriteModel) + switch mode { + case .add: + await viewModel.addActivity(activityWriteModel: viewModel.activityWriteModel) + case .edit(let activityId): + await viewModel.editActivity(activityId: activityId, activityWriteModel: viewModel.activityWriteModel) + } viewModel.resumeViewRoutePop() } }, diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift index f85ca8ee..0233bffb 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeCoordinatorView.swift @@ -31,8 +31,8 @@ struct ResumeCoordinatorView: View { resumeCoordinator.push(next: .myActivityManageView) case .navigateToCareerWrite(let mode): resumeCoordinator.push(next: .myCareerWriteView(mode: mode)) - case .navigateToActivityWrite: - resumeCoordinator.push(next: .myActivityWriteView) + case .navigateToActivityWrite(let mode): + resumeCoordinator.push(next: .myActivityWriteView(mode: mode)) case .navigateToCertificatedEdit: resumeCoordinator.push(next: .myCertificateEdit) case .navigateToCareerManage: @@ -52,8 +52,8 @@ struct ResumeCoordinatorView: View { MyCareerWriteView(viewModel: resumeViewModel, mode: mode) case .myActivityManageView: MyActivityManageView(viewModel: resumeViewModel) - case .myActivityWriteView: - MyActivityWriteView(viewModel: resumeViewModel) + case .myActivityWriteView(let mode): + MyActivityWriteView(viewModel: resumeViewModel, mode: mode) } } } diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift index 30291d3f..df5c85e7 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift @@ -310,21 +310,3 @@ extension ResumeView { } } } - -#Preview { - ResumeView( - viewModel: ResumeViewModel( - fetchJobUseCase: PreviewFetchJobUseCase(), - fetchAcquisitionListUseCase: PreviewFetchAcquisitionListUseCase(), - fetchAcquisitionDetailUseCase: PreviewFetchAcquisitionDetailUseCase(), - deleteAcquisitionUseCase: PreviewDeleteAcquisitionUseCase(), - addCareersUseCase: PreviewAddCareersUseCase(), - deleteCareersUseCase: PreviewDeleteCareersUseCase(), - fetchCareersListUseCase: PreviewFetchCareersListUseCase(), - editCareerUseCase: PreviewEditCareerUseCase(), - addActivityUseCase: PreviewAddActivityUseCase(), - deleteActivityUseCase: PreviewDeleteActivityUseCase(), - fetchActivityListUseCase: PreviewFetchActivityListUseCase() - ) - ) -} diff --git a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift index bb934d11..5fd42811 100644 --- a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift +++ b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift @@ -11,7 +11,7 @@ import os enum ResumeViewRoute: Equatable { case navigateToCareerWrite(mode: CareerWriteMode) - case navigateToActivityWrite + case navigateToActivityWrite(mode: ActivityWriteMode) case navigateToCertificatedEdit case navigateToCareerManage case navigateToActivityManage @@ -24,6 +24,11 @@ enum CareerWriteMode: Hashable { case edit(careerId: Int) } +enum ActivityWriteMode: Hashable { + case add + case edit(activityId: Int) +} + @MainActor final class ResumeViewModel: ObservableObject { @Published var resumeViewRoute: ResumeViewRoute? @@ -37,6 +42,7 @@ final class ResumeViewModel: ObservableObject { @Published var activityWriteModel = ActivityWriteModel() @Published var isCardDetailPresented = false @Published var selectCareerId: Int = 0 + @Published var selectActivityId: Int = 0 var isCareerWriteButtonEnabled: Bool { !careerWriteModel.name.isBlank && !careerWriteModel.place.isBlank && !careerWriteModel.description.isBlank && isPeriodFilled @@ -60,6 +66,7 @@ final class ResumeViewModel: ObservableObject { private let addActivityUseCase: AddActivityUseCase private let deleteActivityUseCase: DeleteActivityUseCase private let fetchActivityListUseCase: FetchActivityListUseCase + private let editActivityUseCase: EditActivityUseCase init( fetchJobUseCase: FetchJobUseCase, @@ -72,7 +79,8 @@ final class ResumeViewModel: ObservableObject { editCareerUseCase: EditCareersUseCase, addActivityUseCase: AddActivityUseCase, deleteActivityUseCase: DeleteActivityUseCase, - fetchActivityListUseCase: FetchActivityListUseCase + fetchActivityListUseCase: FetchActivityListUseCase, + editActivityUseCase: EditActivityUseCase ) { self.fetchJobUseCase = fetchJobUseCase self.fetchAcquisitionListUseCase = fetchAcquisitionListUseCase @@ -85,6 +93,7 @@ final class ResumeViewModel: ObservableObject { self.addActivityUseCase = addActivityUseCase self.deleteActivityUseCase = deleteActivityUseCase self.fetchActivityListUseCase = fetchActivityListUseCase + self.editActivityUseCase = editActivityUseCase } func clearCareerWriteModel() { @@ -126,7 +135,7 @@ extension ResumeViewModel { } func navigateToActivityWrite() { - resumeViewRoute = .navigateToActivityWrite + resumeViewRoute = .navigateToActivityWrite(mode: .edit(activityId: selectActivityId)) } func navigateToCertificatedEdit() { @@ -289,6 +298,18 @@ extension ResumeViewModel { logger.error("❌ 활동 추가 실패: \(error.localizedDescription)") } } + + func editActivity(activityId: Int, activityWriteModel: ActivityWriteModel) async { + let result = await editActivityUseCase.execute(activityId: activityId, request: activityWriteModel.toActivityEntity()) + + switch result { + case .success(_): + logger.info("✅ 대내외활동 수정 성공") + + case .failure(let error): + logger.error("❌ 대내외활동 수정 실패: \(error.localizedDescription)") + } + } } extension ResumeViewModel { From 628dde024d974e280f08ea9238a1e35acc1b7287 Mon Sep 17 00:00:00 2001 From: sangyup Date: Mon, 26 Jan 2026 14:31:17 +0900 Subject: [PATCH 10/14] =?UTF-8?q?[Feat]=20#184=20-=20ActivityEdit=20/=20Ad?= =?UTF-8?q?d=20=EB=B7=B0=20=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResumeActivityListComponent.swift | 8 ++++-- .../Resume/View/MyActivityManageView.swift | 5 +++- .../Resume/View/MyActivityWriteView.swift | 14 +++++----- .../Resume/View/MyCareerManageView.swift | 2 +- .../Presentation/Resume/View/ResumeView.swift | 10 +++---- .../Resume/ViewModel/ResumeViewModel.swift | 27 ++++++++++++++++++- 6 files changed, 49 insertions(+), 17 deletions(-) diff --git a/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift b/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift index 81d0531a..d68aeb0f 100644 --- a/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift +++ b/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift @@ -9,7 +9,8 @@ import SwiftUI struct ResumeActivityListComponent: View { let model: ActivityModel - + let onTapCard: () -> Void + var body: some View { HStack(alignment: .center, spacing: 0) { VStack(alignment: .leading, spacing: 0) { @@ -44,6 +45,9 @@ struct ResumeActivityListComponent: View { Spacer() } + .onTapGesture { + onTapCard() + } } } @@ -55,5 +59,5 @@ struct ResumeActivityListComponent: View { name: "sopt", place: "동아리 36기 기획", description: "서비스 기획 및 아이디어 도출" - )) + ), onTapCard: {}) } diff --git a/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift b/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift index 386c23eb..4b64002a 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift @@ -55,7 +55,10 @@ struct MyActivityManageView: View { LazyVGrid(columns: columns, spacing: 36) { ForEach(viewModel.activitiesList) { item in HStack(alignment: .center, spacing: 0) { - ResumeActivityListComponent(model: item) + ResumeActivityListComponent(model: item, onTapCard: { + viewModel.selectActivity(id: item.activityId) + viewModel.navigateToActivityEdit() + }) .frame(height: 50) Button { diff --git a/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift index 354152e0..e55c310b 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift @@ -31,7 +31,6 @@ struct MyActivityWriteView: View { activityDetailView ResumeWriteButton( action: { - Task { switch mode { case .add: @@ -48,7 +47,12 @@ struct MyActivityWriteView: View { } } .onAppear{ - viewModel.clearActivityWriteModel() + switch mode { + case .add: + viewModel.clearActivityWriteModel() + case .edit(let activityId): + viewModel.prepareActivityEdit(activityId: activityId) + } } .navigationBarBackButtonHidden() .scrollIndicators(.hidden) @@ -61,7 +65,7 @@ extension MyActivityWriteView { private var MyExtracurricularActivityTitleView: some View { Group { HStack(alignment: .center, spacing: 0) { - Text("대내외 활동 추가") + Text(mode == .add ? "대내외 활동 추가" : "대내외 활동 수정") .applyCertiFont(.sub_semibold_20) .foregroundStyle(.grayscale600) .frame(height: 26) @@ -162,8 +166,4 @@ extension MyActivityWriteView { .padding(.bottom, 16) } } - - private func testButtonClicked() { - print("testButtonClicked") - } } diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift index b3ddaa25..48c03b14 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift @@ -56,7 +56,7 @@ struct MyCareerManageView: View { HStack(alignment: .center, spacing: 0) { ResumeCareerListComponent(model: item, onTapCard: { viewModel.selectCareer(id: item.careerId) - viewModel.navigateToCareerEdit2() + viewModel.navigateToCareerEdit() }) .frame(height: 50) diff --git a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift index df5c85e7..e9e8a508 100644 --- a/CERTI-iOS/Presentation/Resume/View/ResumeView.swift +++ b/CERTI-iOS/Presentation/Resume/View/ResumeView.swift @@ -23,8 +23,8 @@ struct ResumeView: View { ResumeMyCertificateView ResumeMyCareerTitleView ResumeMyCareerView - ResumeMyExtracurricularActivityTitleView - ResumeMyExtracurricularActivityView + ResumeMyActivityTitleView + ResumeMyActivityView } } .scrollIndicators(.hidden) @@ -254,7 +254,7 @@ extension ResumeView { } } - private var ResumeMyExtracurricularActivityTitleView: some View { + private var ResumeMyActivityTitleView: some View { HStack(alignment: .center, spacing: 0){ Text("대내외 활동") .applyCertiFont(.sub_semibold_20) @@ -274,7 +274,7 @@ extension ResumeView { .padding(.bottom, 16) } - private var ResumeMyExtracurricularActivityView: some View { + private var ResumeMyActivityView: some View { VStack(alignment: .leading, spacing: 0) { if viewModel.activitiesList.isEmpty { VStack(alignment: .center, spacing: 0) { @@ -299,7 +299,7 @@ extension ResumeView { .padding(.top, 20.5) .padding(.bottom, 29.5) - ResumeActivityListComponent(model: item) + ResumeActivityListComponent(model: item, onTapCard: {}) .frame(height: 74) } .padding(.horizontal, 20) diff --git a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift index 5fd42811..bae2d0df 100644 --- a/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift +++ b/CERTI-iOS/Presentation/Resume/ViewModel/ResumeViewModel.swift @@ -130,11 +130,15 @@ extension ResumeViewModel { resumeViewRoute = .navigateToCareerWrite(mode: .add) } - func navigateToCareerEdit2() { + func navigateToCareerEdit() { resumeViewRoute = .navigateToCareerWrite(mode: .edit(careerId: selectCareerId)) } func navigateToActivityWrite() { + resumeViewRoute = .navigateToActivityWrite(mode: .add) + } + + func navigateToActivityEdit() { resumeViewRoute = .navigateToActivityWrite(mode: .edit(activityId: selectActivityId)) } @@ -337,4 +341,25 @@ extension ResumeViewModel { func selectCareer(id: Int) { selectCareerId = id } + + func prepareActivityEdit(activityId: Int) { + guard let activity = activitiesList.first(where: { $0.activityId == activityId }) else { + return + } + + activityWriteModel = ActivityWriteModel( + startAt: activity.startAt, + endAt: activity.endAt, + name: activity.name, + place: activity.place, + description: activity.description + ) + + isPeriodFilled = true + selectActivityId = activityId + } + + func selectActivity(id: Int) { + selectActivityId = id + } } From e64b4f895bf15cb925a3e8a21559edf26f19a186 Mon Sep 17 00:00:00 2001 From: sangyup Date: Mon, 26 Jan 2026 14:37:32 +0900 Subject: [PATCH 11/14] =?UTF-8?q?[Style]=20#184=20-=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EA=B8=B0/=EC=88=98=EC=A0=95=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resume/Components/ResumeWriteButton.swift | 9 +++------ .../Presentation/Resume/View/MyActivityWriteView.swift | 2 +- .../Presentation/Resume/View/MyCareerWriteView.swift | 6 +----- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/CERTI-iOS/Presentation/Resume/Components/ResumeWriteButton.swift b/CERTI-iOS/Presentation/Resume/Components/ResumeWriteButton.swift index d5c1ccd7..7edf6677 100644 --- a/CERTI-iOS/Presentation/Resume/Components/ResumeWriteButton.swift +++ b/CERTI-iOS/Presentation/Resume/Components/ResumeWriteButton.swift @@ -9,13 +9,15 @@ import SwiftUI struct ResumeWriteButton: View { let action: () -> Void + let buttonText: String + @Binding var textEmpty: Bool var body: some View { Button { action() } label: { - Text("추가하기") + Text(buttonText) .applyCertiFont(.body_semibold_16) .foregroundStyle(textEmpty ? .white : .grayscale400) .frame(maxWidth: .infinity) @@ -27,8 +29,3 @@ struct ResumeWriteButton: View { .disabled(!textEmpty) } } - -private func testButtonClicked() { - print("testButtonClicked") -} - diff --git a/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift index e55c310b..ce3bd483 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyActivityWriteView.swift @@ -40,7 +40,7 @@ struct MyActivityWriteView: View { } viewModel.resumeViewRoutePop() } - }, + }, buttonText: mode == .add ? "추가하기" : "수정하기", textEmpty: .constant(viewModel.isActivityWriteButtonEnabled) ) .padding(.top, 40) diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift index 3ca45894..73c68f52 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerWriteView.swift @@ -40,7 +40,7 @@ struct MyCareerWriteView: View { } viewModel.resumeViewRoutePop() } - }, + }, buttonText: mode == .add ? "추가하기" : "수정하기", textEmpty: .constant(viewModel.isCareerWriteButtonEnabled) ) .padding(.top, 40) @@ -160,8 +160,4 @@ extension MyCareerWriteView { .padding(.horizontal, 20) } } - - private func testButtonClicked() { - print("testButtonClicked") - } } From 019fc38e9bf78aac9b71bebff823ffe7ad0004b1 Mon Sep 17 00:00:00 2001 From: sangyup Date: Mon, 26 Jan 2026 15:23:37 +0900 Subject: [PATCH 12/14] =?UTF-8?q?[Feat]=20#184=20-=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?formatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CERTI-iOS/Global/Extensions/String+.swift | 23 +++++++++++++++---- .../ResumeActivityListComponent.swift | 5 ++-- .../ResumeCareerListComponent.swift | 4 +++- .../Resume/View/MyActivityManageView.swift | 4 ++-- .../Resume/View/MyCareerManageView.swift | 4 ++-- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/CERTI-iOS/Global/Extensions/String+.swift b/CERTI-iOS/Global/Extensions/String+.swift index 08518988..9ca1946c 100644 --- a/CERTI-iOS/Global/Extensions/String+.swift +++ b/CERTI-iOS/Global/Extensions/String+.swift @@ -32,7 +32,7 @@ extension String { // 숫자만 남기기 let filteredPrice = self.filter { $0.isNumber } guard let price = Int(filteredPrice) else { return 0 } - + return price } @@ -48,11 +48,11 @@ extension String { guard let date = inputFormatter.date(from: self) else { return self } - + let outputFormatter = DateFormatter() outputFormatter.locale = Locale(identifier: "ko_KR") outputFormatter.dateFormat = "yyyy년 M월 d일" - + return "\(outputFormatter.string(from: date))" } @@ -71,5 +71,20 @@ extension String { return "\(self.prefix(count))..." } } - + + func toYearMonth() -> String { + let toDateFormatter = DateFormatter() + toDateFormatter.locale = Locale(identifier: "ko_KR") + toDateFormatter.dateFormat = "yyyy.MM.dd" + + let yearMonthFormatter = DateFormatter() + yearMonthFormatter.locale = Locale(identifier: "ko_KR") + yearMonthFormatter.dateFormat = "yyyy.MM" + + guard let date = toDateFormatter.date(from: self) else { + return self + } + + return yearMonthFormatter.string(from: date) + } } diff --git a/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift b/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift index d68aeb0f..a8bafb42 100644 --- a/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift +++ b/CERTI-iOS/Presentation/Resume/Components/ResumeActivityListComponent.swift @@ -14,8 +14,9 @@ struct ResumeActivityListComponent: View { var body: some View { HStack(alignment: .center, spacing: 0) { VStack(alignment: .leading, spacing: 0) { - Text("\(model.startAt) ~ \(model.endAt)") - .applyCertiFont(.caption_regular_12) + let periodText = "\(model.startAt.toYearMonth()) ~ \(model.endAt.toYearMonth())" + + Text(periodText) .applyCertiFont(.caption_regular_12) .foregroundStyle(.grayscale500) .frame(height: 18) diff --git a/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift b/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift index 30d3ba35..bf867db4 100644 --- a/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift +++ b/CERTI-iOS/Presentation/Resume/Components/ResumeCareerListComponent.swift @@ -14,7 +14,9 @@ struct ResumeCareerListComponent: View { var body: some View { HStack(alignment: .center, spacing: 0) { VStack(alignment: .leading, spacing: 0) { - Text("\(model.startAt) ~ \(model.endAt)") + let periodText = "\(model.startAt.toYearMonth()) ~ \(model.endAt.toYearMonth())" + + Text(periodText) .applyCertiFont(.caption_regular_12) .foregroundStyle(.grayscale500) .frame(height: 18) diff --git a/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift b/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift index 4b64002a..d4780fd3 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyActivityManageView.swift @@ -41,7 +41,7 @@ struct MyActivityManageView: View { .background(.purplewhite) .clipShape(RoundedRectangle(cornerRadius: 8)) } - .padding(.top, 44) + .padding(.top, 16) .padding(.leading, 20) @@ -49,7 +49,7 @@ struct MyActivityManageView: View { .applyCertiFont(.sub_semibold_20) .foregroundStyle(.grayscale600) .frame(height: 26) - .padding(.top, 32) + .padding(.top, 56) .padding(.leading, 20) LazyVGrid(columns: columns, spacing: 36) { diff --git a/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift index 48c03b14..95cb3294 100644 --- a/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift +++ b/CERTI-iOS/Presentation/Resume/View/MyCareerManageView.swift @@ -41,14 +41,14 @@ struct MyCareerManageView: View { .background(.purplewhite) .clipShape(RoundedRectangle(cornerRadius: 8)) } - .padding(.top, 44) + .padding(.top, 16) .padding(.leading, 20) Text("경력사항 수정") .applyCertiFont(.sub_semibold_20) .foregroundStyle(.grayscale600) .frame(height: 26) - .padding(.top, 32) + .padding(.top, 56) .padding(.leading, 20) LazyVGrid(columns: columns, spacing: 36) { From 13db1276a2a82d12df4e23fcbddad5e7b319079d Mon Sep 17 00:00:00 2001 From: sangyup Date: Mon, 26 Jan 2026 15:46:59 +0900 Subject: [PATCH 13/14] =?UTF-8?q?[Fix]=20#184=20-=20Career=20Add/Edit=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CERTI-iOS/Data/Network/Careers/CareersService.swift | 12 ++++++------ .../Data/Repositories/DefaultCareersRepository.swift | 4 ++-- .../Interfaces/Repositories/CareersRepository.swift | 4 ++-- .../Domain/UseCases/Careers/AddCareersUseCase.swift | 4 ++-- .../Domain/UseCases/Careers/EditCareersUseCase.swift | 4 ++-- .../PreviewUseCases/ResumePreviewUseCase.swift | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CERTI-iOS/Data/Network/Careers/CareersService.swift b/CERTI-iOS/Data/Network/Careers/CareersService.swift index 6b85787c..f54eb630 100644 --- a/CERTI-iOS/Data/Network/Careers/CareersService.swift +++ b/CERTI-iOS/Data/Network/Careers/CareersService.swift @@ -12,8 +12,8 @@ import Moya protocol CareersServiceProtocol { func fetchCareersList() async -> Result func deledteCareers(id: Int) async -> Result - func addCareer(request: AddCareerRequestDTO) async -> Result - func editCareer(careerId:Int, request: EditCareerRequestDTO) async -> Result + func addCareer(request: AddCareerRequestDTO) async -> Result + func editCareer(careerId:Int, request: EditCareerRequestDTO) async -> Result } final class CareersService: BaseService, CareersServiceProtocol { @@ -27,11 +27,11 @@ final class CareersService: BaseService, CareersServiceProtocol { return await requestVoid(provider, .deleteCareers(id: id)) } - func addCareer(request: AddCareerRequestDTO) async -> Result { - return await requestDecodable(provider, .addCareer(request: request)) + func addCareer(request: AddCareerRequestDTO) async -> Result { + return await requestVoid(provider, .addCareer(request: request)) } - func editCareer(careerId:Int, request: EditCareerRequestDTO) async -> Result { - return await requestDecodable(provider, .editCareer(careerId: careerId, request: request)) + func editCareer(careerId:Int, request: EditCareerRequestDTO) async -> Result { + return await requestVoid(provider, .editCareer(careerId: careerId, request: request)) } } diff --git a/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift b/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift index 88b3e575..2beb6272 100644 --- a/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift +++ b/CERTI-iOS/Data/Repositories/DefaultCareersRepository.swift @@ -34,12 +34,12 @@ final class DefaultCareersRepository: CareersRepository { return await service.deledteCareers(id: id) } - func addCareer(request: CareerEntity) async -> Result { + func addCareer(request: CareerEntity) async -> Result { let requestDTO = request.toAddCareerRequestDTO() return await service.addCareer(request: requestDTO) } - func editCareer(careerId: Int, request: CareerEntity) async -> Result { + func editCareer(careerId: Int, request: CareerEntity) async -> Result { let requestDTO = request.toEditCareerRequestDTO() return await service.editCareer(careerId: careerId, request: requestDTO) } diff --git a/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift b/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift index 297c5993..6367cd3c 100644 --- a/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift +++ b/CERTI-iOS/Domain/Interfaces/Repositories/CareersRepository.swift @@ -12,6 +12,6 @@ import Moya protocol CareersRepository { func fetchCareersList() async -> Result func deleteCareers(id: Int) async -> Result - func addCareer(request: CareerEntity) async -> Result - func editCareer(careerId:Int, request: CareerEntity) async -> Result + func addCareer(request: CareerEntity) async -> Result + func editCareer(careerId:Int, request: CareerEntity) async -> Result } diff --git a/CERTI-iOS/Domain/UseCases/Careers/AddCareersUseCase.swift b/CERTI-iOS/Domain/UseCases/Careers/AddCareersUseCase.swift index d0ea2b7a..fa252760 100644 --- a/CERTI-iOS/Domain/UseCases/Careers/AddCareersUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/Careers/AddCareersUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol AddCareersUseCase { - func execute(request: CareerEntity) async -> Result + func execute(request: CareerEntity) async -> Result } final class DefaultAddCareersUseCase: AddCareersUseCase { @@ -18,7 +18,7 @@ final class DefaultAddCareersUseCase: AddCareersUseCase { self.repository = repository } - func execute(request: CareerEntity) async -> Result { + func execute(request: CareerEntity) async -> Result { return await repository.addCareer(request: request) } } diff --git a/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift b/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift index 40bca1bd..01b3df57 100644 --- a/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/Careers/EditCareersUseCase.swift @@ -8,7 +8,7 @@ import Foundation protocol EditCareersUseCase { - func execute(careerId:Int, request: CareerEntity) async -> Result + func execute(careerId:Int, request: CareerEntity) async -> Result } final class DefaultEditCareersUseCase: EditCareersUseCase { @@ -18,7 +18,7 @@ final class DefaultEditCareersUseCase: EditCareersUseCase { self.repository = repository } - func execute(careerId: Int, request: CareerEntity) async -> Result { + func execute(careerId: Int, request: CareerEntity) async -> Result { return await repository.editCareer(careerId: careerId, request: request) } } diff --git a/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift b/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift index 41d8b583..67da5177 100644 --- a/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift +++ b/CERTI-iOS/Domain/UseCases/PreviewUseCases/ResumePreviewUseCase.swift @@ -65,8 +65,8 @@ struct PreviewFetchCareersListUseCase: FetchCareersListUseCase { } struct PreviewAddCareersUseCase: AddCareersUseCase { - func execute(request: CareerEntity) async -> Result { - .success(true) + func execute(request: CareerEntity) async -> Result { + return .success(()) } } @@ -77,8 +77,8 @@ struct PreviewDeleteCareersUseCase: DeleteCareersUseCase { } struct PreviewEditCareerUseCase: EditCareersUseCase { - func execute(careerId: Int, request: CareerEntity) async -> Result { - .success(true) + func execute(careerId: Int, request: CareerEntity) async -> Result { + return .success(()) } } From 2419e62673e1241fe97f317261a61392b33713ec Mon Sep 17 00:00:00 2001 From: sangyup Date: Tue, 10 Feb 2026 22:50:39 +0900 Subject: [PATCH 14/14] =?UTF-8?q?[Fix]=20#184=20-=20DateFormatter=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CERTI-iOS/Global/Extensions/Date+.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CERTI-iOS/Global/Extensions/Date+.swift b/CERTI-iOS/Global/Extensions/Date+.swift index 311f6ab5..e75d6e52 100644 --- a/CERTI-iOS/Global/Extensions/Date+.swift +++ b/CERTI-iOS/Global/Extensions/Date+.swift @@ -13,10 +13,7 @@ extension Date { formatter.locale = Locale(identifier: "ko_KR") let formats = [ - "yyyy.MM.dd", - - // TODO: - 서버에서 연,월,일 주면 삭제 - "yyyy.MM" + "yyyy.MM.dd" ] for format in formats {