diff --git a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift
index af9f6a96..a75b3866 100644
--- a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift
+++ b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift
@@ -38,5 +38,12 @@ public struct DomainAssembly: Assembly {
repository: repository
)
}
+
+ container.register(WorkerProfileUseCase.self) { resolver in
+ let repository = resolver.resolve(UserProfileRepository.self)!
+
+ return DefaultWorkerProfileUseCase(repository: repository)
+ }
+
}
}
diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator+Extension.swift b/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator+Extension.swift
index a8beb774..0d965c11 100644
--- a/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator+Extension.swift
+++ b/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator+Extension.swift
@@ -1,6 +1,6 @@
//
// AuthCoordinator+Extension.swift
-// AuthFeature
+// Idle-iOS
//
// Created by choijunios on 6/30/24.
//
diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator.swift
index 73f4e660..699393c2 100644
--- a/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator.swift
+++ b/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator.swift
@@ -1,6 +1,6 @@
//
// AuthCoordinator.swift
-// AuthFeature
+// Idle-iOS
//
// Created by choijunios on 6/30/24.
//
@@ -11,6 +11,11 @@ import AuthFeature
class AuthCoordinator: ParentCoordinator {
+ struct Dependency {
+ let navigationController: UINavigationController
+ let injector: Injector
+ }
+
var childCoordinators: [Coordinator] = []
var parent: ParentCoordinator?
diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift b/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift
index 03d40b12..f921e334 100644
--- a/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift
+++ b/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift
@@ -13,6 +13,11 @@ import ConcreteRepository
class CenterAuthCoorinator: ParentCoordinator {
+ struct Dependency {
+ let navigationController: UINavigationController
+ let injector: Injector
+ }
+
var childCoordinators: [Coordinator] = []
var parent: AuthCoordinatable?
diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/Worker/WorkerAuthCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Auth/Worker/WorkerAuthCoordinator.swift
index 8f57a72d..bf39c0f7 100644
--- a/project/Projects/App/Sources/RootCoordinator/Auth/Worker/WorkerAuthCoordinator.swift
+++ b/project/Projects/App/Sources/RootCoordinator/Auth/Worker/WorkerAuthCoordinator.swift
@@ -1,6 +1,6 @@
//
// WorkerAuthCoordinator.swift
-// AuthFeature
+// Idle-iOS
//
// Created by choijunios on 6/30/24.
//
@@ -12,6 +12,11 @@ import AuthFeature
class WorkerAuthCoordinator: ParentCoordinator {
+ struct Dependency {
+ let navigationController: UINavigationController
+ let injector: Injector
+ }
+
var childCoordinators: [Coordinator] = []
var navigationController: UINavigationController
diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift
index 6cbad864..f681ed47 100644
--- a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift
+++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift
@@ -9,8 +9,15 @@ import UIKit
import DSKit
import PresentationCore
import RootFeature
+import UseCaseInterface
class CenterMainCoordinator: CenterMainCoordinatable {
+
+ struct Dependency {
+ let navigationController: UINavigationController
+ let injector: Injector
+ }
+
var childCoordinators: [Coordinator] = []
var parent: ParentCoordinator?
@@ -46,7 +53,7 @@ class CenterMainCoordinator: CenterMainCoordinatable {
func createNavForTab(tab: CenterMainScreen) -> UINavigationController {
let tabNavController = UINavigationController()
- tabNavController.setNavigationBarHidden(false, animated: false)
+ tabNavController.setNavigationBarHidden(true, animated: false)
startTabCoordinator(
tab: tab,
@@ -58,13 +65,16 @@ class CenterMainCoordinator: CenterMainCoordinatable {
// #2. 생성한 컨트롤러를 각 탭별 Coordinator에 전달
func startTabCoordinator(tab: CenterMainScreen, navigationController: UINavigationController) {
- var coordinator: ChildCoordinator!
+ var coordinator: Coordinator!
switch tab {
case .recruitmentManage:
coordinator = RecruitmentManagementCoordinator(
- parent: self,
- navigationController: navigationController
+ dependency: .init(
+ parent: self,
+ navigationController: navigationController,
+ workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self)
+ )
)
case .setting:
@@ -95,6 +105,7 @@ enum CenterMainScreen: Int, CaseIterable {
}
}
+// Test
extension CenterMainCoordinator {
/// 센터 정보등록 창을 표시합니다.
diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift
similarity index 93%
rename from project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift
rename to project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift
index 3729246a..eb2538f4 100644
--- a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift
+++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift
@@ -12,6 +12,11 @@ import PresentationCore
import UseCaseInterface
class CenterProfileRegisterCoordinator: CenterProfileRegisterCoordinatable {
+
+ struct Dependency {
+ let navigationController: UINavigationController
+ let injector: Injector
+ }
var childCoordinators: [Coordinator] = []
diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/RegisterPostCoordinator.swift
similarity index 93%
rename from project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift
rename to project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/RegisterPostCoordinator.swift
index e58d5833..ad77cf20 100644
--- a/project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift
+++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/RegisterPostCoordinator.swift
@@ -14,6 +14,11 @@ import UseCaseInterface
class RegisterRecruitmentPostCoordinator: RegisterRecruitmentPostCoordinatable {
+ struct Dependency {
+ let navigationController: UINavigationController
+ let injector: Injector
+ }
+
var childCoordinators: [Coordinator] = []
var parent: ParentCoordinator?
diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift
index a6233de5..1bdea884 100644
--- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift
+++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift
@@ -11,6 +11,12 @@ import PresentationCore
import RootFeature
class WorkerMainCoordinator: ParentCoordinator {
+
+ struct Dependency {
+ let navigationController: UINavigationController
+ let injector: Injector
+ }
+
var childCoordinators: [Coordinator] = []
var parent: ParentCoordinator?
diff --git a/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift
index c4fe48ff..e0e9da8e 100644
--- a/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift
+++ b/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift
@@ -8,13 +8,13 @@
import UIKit
import PresentationCore
-struct Dependency {
- let navigationController: UINavigationController
- let injector: Injector
-}
-
class RootCoordinator: ParentCoordinator {
+ struct Dependency {
+ let navigationController: UINavigationController
+ let injector: Injector
+ }
+
var childCoordinators: [Coordinator] = []
let navigationController: UINavigationController
diff --git a/project/Projects/Domain/Entity/Error/RecruitmentPost/CenterEmployCardVO.swift b/project/Projects/Domain/Entity/Error/RecruitmentPost/CenterEmployCardVO.swift
new file mode 100644
index 00000000..e7aa6f09
--- /dev/null
+++ b/project/Projects/Domain/Entity/Error/RecruitmentPost/CenterEmployCardVO.swift
@@ -0,0 +1,63 @@
+//
+// CenterEmployCardVO.swift
+// Entity
+//
+// Created by choijunios on 8/13/24.
+//
+
+import Foundation
+
+public class CenterEmployCardVO {
+
+ public let postId: String
+ public let isOngoing: Bool
+
+ // For rendering
+ public let startDay: String
+ public let endDay: String?
+ public let postTitle: String
+ public let name: String
+ public let careGrade: CareGrade
+ public let age: Int
+ public let gender: Gender
+ public let applicantCount: Int
+
+ public init(
+ isOngoing: Bool,
+ postId: String,
+ startDay: String,
+ endDay: String?,
+ postTitle: String,
+ name: String,
+ careGrade: CareGrade,
+ age: Int,
+ gender: Gender,
+ applicantCount: Int
+ ) {
+ self.isOngoing = isOngoing
+ self.postId = postId
+ self.startDay = startDay
+ self.endDay = endDay
+ self.postTitle = postTitle
+ self.name = name
+ self.careGrade = careGrade
+ self.age = age
+ self.gender = gender
+ self.applicantCount = applicantCount
+ }
+
+ public static var mock: CenterEmployCardVO {
+ .init(
+ isOngoing: true,
+ postId: "00-00000-00000",
+ startDay: "12:00",
+ endDay: nil,
+ postTitle: "서울특별시 강남구 신사동",
+ name: "홍길동",
+ careGrade: .one,
+ age: 78,
+ gender: .female,
+ applicantCount: 78
+ )
+ }
+}
diff --git a/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift b/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift
new file mode 100644
index 00000000..89f391a2
--- /dev/null
+++ b/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift
@@ -0,0 +1,45 @@
+//
+// PostApplicantVO.swift
+// Entity
+//
+// Created by choijunios on 8/13/24.
+//
+
+import Foundation
+
+public struct PostApplicantVO {
+
+ //
+ public let workerId: String
+
+ // For Render
+ public let profileUrl: URL?
+ public let isJobFinding: Bool
+ public let isStared: Bool
+ public let name: String
+ public let age: Int
+ public let gender: Gender
+ public let expYear: Int?
+
+ public init(workerId: String, profileUrl: URL?, isJobFinding: Bool, isStared: Bool, name: String, age: Int, gender: Gender, expYear: Int?) {
+ self.workerId = workerId
+ self.profileUrl = profileUrl
+ self.isJobFinding = isJobFinding
+ self.isStared = isStared
+ self.name = name
+ self.age = age
+ self.gender = gender
+ self.expYear = expYear
+ }
+
+ public static let mock: PostApplicantVO = .init(
+ workerId: "testworkerId",
+ profileUrl: URL(string: "https://dummyimage.com/600x400/00ffbf/0011ff&text=worker+profile"),
+ isJobFinding: false,
+ isStared: false,
+ name: "홍길동",
+ age: 51,
+ gender: .female,
+ expYear: nil
+ )
+}
diff --git a/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/gray0.colorset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/gray0.colorset/Contents.json
new file mode 100644
index 00000000..951b9076
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/gray0.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xFF",
+ "green" : "0xFF",
+ "red" : "0xFE"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xFF",
+ "green" : "0xFF",
+ "red" : "0xFE"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/Contents.json
new file mode 100644
index 00000000..e3c8da20
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "bell.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/bell.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/bell.svg
new file mode 100644
index 00000000..b113db53
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/bell.svg
@@ -0,0 +1,3 @@
+
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/Contents.json
new file mode 100644
index 00000000..f3c70e18
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "post_check.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/post_check.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/post_check.svg
new file mode 100644
index 00000000..6f023a6a
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/post_check.svg
@@ -0,0 +1,10 @@
+
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/Contents.json
new file mode 100644
index 00000000..88de3996
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "post_edit.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/post_edit.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/post_edit.svg
new file mode 100644
index 00000000..5223407a
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/post_edit.svg
@@ -0,0 +1,10 @@
+
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/workerProfilePlaceholder.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/workerProfilePlaceholder.svg
deleted file mode 100644
index 3c7f3e32..00000000
--- a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/workerProfilePlaceholder.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/Contents.json
similarity index 69%
rename from project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/Contents.json
rename to project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/Contents.json
index d4ce8ef1..9043bdbb 100644
--- a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/Contents.json
+++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "workerProfilePlaceholder.svg",
+ "filename" : "worker_profile_placeholder.svg",
"idiom" : "universal"
}
],
diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/worker_profile_placeholder.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/worker_profile_placeholder.svg
new file mode 100644
index 00000000..68732a5e
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/worker_profile_placeholder.svg
@@ -0,0 +1,12 @@
+
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift
new file mode 100644
index 00000000..a5647fa8
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift
@@ -0,0 +1,103 @@
+//
+// ImagePrefixButton.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+
+public class ImageTextButton: TappableUIView {
+
+ public enum ImagePose {
+ case prefix
+ case postfix
+ }
+
+ let imagePose: ImagePose
+
+ let icon: UIImageView = {
+ let icon = UIImageView()
+ icon.contentMode = .scaleAspectFit
+ return icon
+ }()
+
+ public let label: IdleLabel = {
+ let label = IdleLabel(typography: .Body3)
+ return label
+ }()
+
+ private let disposeBag = DisposeBag()
+
+ public init(iconImage: UIImage, position: ImagePose) {
+ self.imagePose = position
+ super.init()
+
+ icon.image = iconImage
+
+ setAppearance()
+ setLayout()
+ setObservable()
+ }
+
+ public required init?(coder: NSCoder) { fatalError() }
+
+ private func setAppearance() {
+
+ }
+
+ private func setLayout() {
+ let mainStack = imagePose == .prefix ? HStack([
+ icon,
+ label
+ ], spacing: 2, alignment: .center) : HStack([
+ label,
+ icon
+ ], spacing: 2, alignment: .center)
+
+ NSLayoutConstraint.activate([
+ icon.widthAnchor.constraint(equalToConstant: 24),
+ icon.heightAnchor.constraint(equalTo: icon.widthAnchor),
+ ])
+
+ [
+ mainStack
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ self.addSubview(mainStack)
+ }
+
+ NSLayoutConstraint.activate([
+ mainStack.topAnchor.constraint(equalTo: self.topAnchor),
+ mainStack.leftAnchor.constraint(equalTo: self.leftAnchor),
+ mainStack.rightAnchor.constraint(equalTo: self.rightAnchor),
+ mainStack.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ ])
+ }
+
+ private func setObservable() {
+ self.rx.tap
+ .subscribe { [weak self] _ in
+ self?.alpha = 0.5
+ UIView.animate(withDuration: 0.35) { [weak self] in
+ self?.alpha = 1.0
+ }
+ }
+ .disposed(by: disposeBag)
+ }
+}
+
+@available(iOS 17.0, *)
+#Preview("Preview", traits: .defaultLayout) {
+
+ let btn = ImageTextButton(
+ iconImage: DSKitAsset.Icons.editPhoto.image,
+ position: .postfix
+ )
+ btn.label.textString = "공고 수정"
+ btn.tintColor = .black
+ return btn
+}
+
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift
new file mode 100644
index 00000000..9a2daceb
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift
@@ -0,0 +1,200 @@
+//
+// CenterEmployCard.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+import Entity
+
+public struct CenterEmployCardRO {
+ public let startDay: String
+ public let endDay: String
+ public let postTitle: String
+ public let nameText: String
+ public let careGradeText: String
+ public let ageText: String
+ public let genderText: String
+ public let applicantCount: Int
+
+ public init(startDay: String, endDay: String, postTitle: String, nameText: String, careGradeText: String, ageText: String, genderText: String, applicantCount: Int) {
+ self.startDay = startDay
+ self.endDay = endDay
+ self.postTitle = postTitle
+ self.nameText = nameText
+ self.careGradeText = careGradeText
+ self.ageText = ageText
+ self.genderText = genderText
+ self.applicantCount = applicantCount
+ }
+
+ public static let mock: CenterEmployCardRO = .init(
+ startDay: "2024. 07. 10",
+ endDay: "2024. 07. 31",
+ postTitle: "서울특별시 강남구 신사동",
+ nameText: "홍길동",
+ careGradeText: "1등급",
+ ageText: "78세",
+ genderText: "여성",
+ applicantCount: 2
+ )
+
+
+ public static func create(_ vo: CenterEmployCardVO) -> CenterEmployCardRO {
+ .init(
+ startDay: vo.startDay,
+ endDay: vo.endDay ?? "채용 시까지",
+ postTitle: vo.postTitle,
+ nameText: vo.name,
+ careGradeText: "\(vo.careGrade.textForCellBtn)등급",
+ ageText: "\(vo.age)세",
+ genderText: vo.gender.twoLetterKoreanWord,
+ applicantCount: vo.applicantCount
+ )
+ }
+}
+
+public protocol CenterEmployCardViewModelable {
+
+ // Output
+ var renderObject: Driver? { get }
+
+ // Input
+ var cardClicked: PublishRelay { get }
+
+ // - Buttons
+ var checkApplicantBtnClicked: PublishRelay { get }
+ var editPostBtnClicked: PublishRelay { get }
+ var terminatePostBtnClicked: PublishRelay { get }
+}
+
+public class CenterEmployCard: TappableUIView {
+
+ // Init
+
+ // View
+
+ // Row1, 2, 3 View
+ let centerEmployCardInfoView = CenterEmployCardInfoView()
+
+ // Row4
+ let checkApplicantsButton: IdlePrimaryCardButton = {
+ let btn = IdlePrimaryCardButton(level: .medium)
+ btn.label.textString = ""
+ return btn
+ }()
+
+ // Row5
+ let editPostButton: ImageTextButton = {
+ let button = ImageTextButton(
+ iconImage: DSKitAsset.Icons.postEdit.image,
+ position: .prefix
+ )
+ button.icon.tintColor = DSKitAsset.Colors.gray300.color
+ button.label.textString = "공고 수정"
+ button.label.attrTextColor = DSKitAsset.Colors.gray500.color
+
+ return button
+ }()
+ let terminatePostButton: ImageTextButton = {
+ let button = ImageTextButton(
+ iconImage: DSKitAsset.Icons.postCheck.image,
+ position: .prefix
+ )
+ button.icon.tintColor = DSKitAsset.Colors.gray300.color
+ button.label.textString = "채용 종료"
+ button.label.attrTextColor = DSKitAsset.Colors.gray500.color
+
+ return button
+ }()
+
+
+ // Observable
+ private let disposeBag = DisposeBag()
+
+ public override init() {
+ super.init()
+
+ setAppearance()
+ setLayout()
+ setObservable()
+ }
+
+ public required init?(coder: NSCoder) { fatalError() }
+
+ private func setAppearance() {
+ self.backgroundColor = .white
+ self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor
+ self.layer.borderWidth = 1.0
+ self.layer.cornerRadius = 12
+ }
+
+ private func setLayout() {
+
+ self.layoutMargins = .init(top: 16, left: 16, bottom: 16, right: 16)
+
+ let buttonStack = HStack([
+ editPostButton,
+ terminatePostButton,
+ ], spacing: 4)
+
+ let contentStack = VStack([
+ HStack([centerEmployCardInfoView, Spacer()]),
+ Spacer(height: 12),
+ checkApplicantsButton,
+ HStack([buttonStack, Spacer()])
+ ], alignment: .fill)
+
+ [
+ contentStack
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ self.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ contentStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor),
+ contentStack.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor),
+ contentStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor),
+ contentStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor),
+ ])
+ }
+
+ private func setObservable() { }
+
+ public func bind(ro: CenterEmployCardRO) {
+
+ centerEmployCardInfoView.durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)"
+ centerEmployCardInfoView.postTitleLabel.textString = ro.postTitle
+ centerEmployCardInfoView.nameLabel.textString = ro.nameText
+ centerEmployCardInfoView.informationLabel.textString = "\(ro.careGradeText) \(ro.ageText) \(ro.genderText)"
+ checkApplicantsButton.label.textString = "지원자 \(ro.applicantCount)명 조회"
+ }
+}
+
+fileprivate class TextVM: CenterEmployCardViewModelable {
+
+ public let publishObect: PublishRelay = .init()
+
+ var renderObject: RxCocoa.Driver?
+
+ var cardClicked: RxRelay.PublishRelay = .init()
+ var checkApplicantBtnClicked: RxRelay.PublishRelay = .init()
+ var editPostBtnClicked: RxRelay.PublishRelay = .init()
+ var terminatePostBtnClicked: RxRelay.PublishRelay = .init()
+
+ init() {
+
+ renderObject = publishObect.asDriver(onErrorJustReturn: .mock)
+ }
+}
+
+@available(iOS 17.0, *)
+#Preview("Preview", traits: .defaultLayout) {
+ let btn = CenterEmployCard()
+ btn.bind(ro: .mock)
+ return btn
+}
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift
new file mode 100644
index 00000000..bb6acaf3
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift
@@ -0,0 +1,93 @@
+//
+// CenterEmployCardCell.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+import Entity
+
+public class CenterEmployCardCell: UITableViewCell {
+
+ var viewModel: CenterEmployCardViewModelable?
+
+ public static let identifier = String(describing: CenterEmployCardCell.self)
+
+ let cardView = CenterEmployCard()
+
+ private var disposables: [Disposable?]?
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ setAppearance()
+ setLayout()
+ }
+ public required init?(coder: NSCoder) { fatalError() }
+
+ public override func prepareForReuse() {
+ viewModel = nil
+
+ disposables?.forEach { $0?.dispose() }
+ disposables = nil
+ }
+
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+
+ contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 20, bottom: 8, right: 20))
+ }
+
+ func setAppearance() { }
+
+ func setLayout() {
+
+ [
+ cardView
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ cardView.topAnchor.constraint(equalTo: contentView.topAnchor),
+ cardView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
+ cardView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
+ cardView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+ ])
+ }
+
+ public func bind(viewModel: CenterEmployCardViewModelable) {
+
+ self.viewModel = viewModel
+
+ let disposables: [Disposable?] = [
+ // Output
+ viewModel
+ .renderObject?
+ .drive(onNext: { [cardView] ro in
+ cardView.bind(ro: ro)
+ }),
+
+ // Input
+ cardView.rx.tap
+ .bind(to: viewModel.cardClicked),
+
+ cardView.checkApplicantsButton
+ .rx.tap
+ .bind(to: viewModel.checkApplicantBtnClicked),
+
+ cardView.editPostButton
+ .rx.tap
+ .bind(to: viewModel.editPostBtnClicked),
+
+ cardView.terminatePostButton
+ .rx.tap
+ .bind(to: viewModel.terminatePostBtnClicked),
+ ]
+
+ self.disposables = disposables
+ }
+}
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardInfoView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardInfoView.swift
new file mode 100644
index 00000000..aa095f36
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardInfoView.swift
@@ -0,0 +1,70 @@
+//
+// CenterEmployCardInfoView.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+
+public class CenterEmployCardInfoView: VStack {
+ // Row1
+ let durationLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Body3)
+ label.attrTextColor = DSKitAsset.Colors.gray300.color
+ return label
+ }()
+
+ // Row2
+ let postTitleLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Subtitle2)
+ return label
+ }()
+
+ // Row3
+ let nameLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Body2)
+ label.attrTextColor = DSKitAsset.Colors.gray500.color
+ return label
+ }()
+ let informationLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Body2)
+ label.attrTextColor = DSKitAsset.Colors.gray500.color
+ return label
+ }()
+
+ init() {
+ super.init([], alignment: .leading)
+ setLayout()
+ }
+
+ public required init(coder: NSCoder) { fatalError() }
+
+ func setLayout() {
+
+ // InfoLabel
+ let divider = UIView()
+ divider.backgroundColor = DSKitAsset.Colors.gray300.color
+ let infoStack = HStack([
+ nameLabel,
+ divider,
+ informationLabel,
+ ], spacing: 8)
+
+ NSLayoutConstraint.activate([
+ divider.widthAnchor.constraint(equalToConstant: 1),
+ divider.topAnchor.constraint(equalTo: infoStack.topAnchor, constant: 5),
+ divider.bottomAnchor.constraint(equalTo: infoStack.bottomAnchor, constant: -5),
+ ])
+
+ [
+ durationLabel,
+ Spacer(height: 4),
+ postTitleLabel,
+ Spacer(height: 2),
+ infoStack,
+ ].forEach {
+ self.addArrangedSubview($0)
+ }
+ }
+}
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift
new file mode 100644
index 00000000..f0c0af45
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift
@@ -0,0 +1,278 @@
+//
+// ApplicantCardView.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import PresentationCore
+import RxCocoa
+import RxSwift
+import Entity
+import Kingfisher
+import Entity
+
+public struct ApplicantCardRO {
+
+ public let profileUrl: URL?
+ public let isJobFinding: Bool
+// public let isStared: Bool
+ public let name: String
+ public let ageText: String
+ public let genderText: String
+ public let expText: String
+
+ public init(
+ profileUrl: URL?,
+ isJobFinding: Bool,
+// isStared: Bool,
+ name: String,
+ ageText: String,
+ genderText: String,
+ expText: String
+ ) {
+ self.profileUrl = profileUrl
+ self.isJobFinding = isJobFinding
+// self.isStared = isStared
+ self.name = name
+ self.ageText = ageText
+ self.genderText = genderText
+ self.expText = expText
+ }
+
+ public static let mock: ApplicantCardRO = .init(
+ profileUrl: URL(string: "https://dummyimage.com/600x400/00ffbf/0011ff&text=worker+profile"),
+ isJobFinding: false,
+// isStared: false,
+ name: "홍길동",
+ ageText: "51세",
+ genderText: "여성",
+ expText: "1년차"
+ )
+
+ public static func create(vo: PostApplicantVO) -> ApplicantCardRO {
+ .init(
+ profileUrl: vo.profileUrl,
+ isJobFinding: vo.isJobFinding,
+// isStared: vo.isStared,
+ name: vo.name,
+ ageText: "\(vo.age)세",
+ genderText: vo.gender.twoLetterKoreanWord,
+ expText: vo.expYear == nil ? "신입" : "\(vo.expYear!)년차"
+ )
+ }
+}
+
+public protocol ApplicantCardViewModelable {
+
+ // - Buttons
+ var showProfileButtonClicked: PublishRelay { get }
+ var employButtonClicked: PublishRelay { get }
+ var staredThisWorker: PublishRelay { get }
+
+ // Output
+ var renderObject: Driver? { get }
+}
+
+public class ApplicantCard: UIView {
+
+ // View
+ // Profile
+ let profileImageContainer: UIImageView = {
+
+ let view = UIImageView()
+ view.backgroundColor = DSKitAsset.Colors.orange100.color
+ view.layer.cornerRadius = 36
+ view.clipsToBounds = true
+ view.image = DSKitAsset.Icons.workerProfilePlaceholder.image
+ view.contentMode = .scaleAspectFit
+
+ return view
+ }()
+ let workerProfileImage: UIImageView = {
+
+ let imageView = UIImageView()
+ imageView.contentMode = .scaleAspectFill
+ imageView.clipsToBounds = true
+
+ return imageView
+ }()
+ // Star
+// public let starButton: IconWithColorStateButton = {
+// let button = IconWithColorStateButton(
+// representImage: DSKitAsset.Icons.subscribeStar.image,
+// normalColor: DSKitAsset.Colors.gray200.color,
+// accentColor: DSKitAsset.Colors.orange300.color
+// )
+// return button
+// }()
+
+ // Row1
+ let workingTag: TagLabel = {
+ let label = TagLabel(
+ text: "",
+ typography: .caption,
+ textColor: DSKitAsset.Colors.orange500.color,
+ backgroundColor: DSKitAsset.Colors.orange100.color
+ )
+ return label
+ }()
+
+ // Row2
+ let nameLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Subtitle2)
+
+ return label
+ }()
+
+ // Row3
+ let infoLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Body3)
+ label.attrTextColor = DSKitAsset.Colors.gray500.color
+ return label
+ }()
+ let expLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Body3)
+ label.attrTextColor = DSKitAsset.Colors.gray500.color
+ return label
+ }()
+
+ // 버튼들
+ let showProfileButton: IdleSecondaryButton = {
+ let button = IdleSecondaryButton(level: .medium)
+ button.label.textString = "프로필 보기"
+ return button
+ }()
+ let employButton: IdlePrimaryButton = {
+ let button = IdlePrimaryButton(level: .medium)
+ button.label.textString = "채용하기"
+ return button
+ }()
+
+ // Observable
+ private let disposeBag = DisposeBag()
+
+ public init() {
+ super.init(frame: .zero)
+ setAppearance()
+ setLayout()
+ setObservable()
+ }
+
+ public required init?(coder: NSCoder) { fatalError() }
+
+ private func setAppearance() {
+ self.backgroundColor = .white
+ self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor
+ self.layer.borderWidth = 1.0
+ self.layer.cornerRadius = 12
+ }
+
+ private func setLayout() {
+
+ self.layoutMargins = .init(top: 12, left: 16, bottom: 12, right: 16)
+
+ profileImageContainer.addSubview(workerProfileImage)
+ workerProfileImage.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+
+ profileImageContainer.widthAnchor.constraint(equalToConstant: 72),
+ profileImageContainer.heightAnchor.constraint(equalTo: profileImageContainer.widthAnchor),
+
+ workerProfileImage.topAnchor.constraint(equalTo: profileImageContainer.topAnchor),
+ workerProfileImage.leftAnchor.constraint(equalTo: profileImageContainer.leftAnchor),
+ workerProfileImage.rightAnchor.constraint(equalTo: profileImageContainer.rightAnchor),
+ workerProfileImage.bottomAnchor.constraint(equalTo: profileImageContainer.bottomAnchor),
+ ])
+
+ let nameTitleLabel: IdleLabel = .init(typography: .Body3)
+ nameTitleLabel.textString = "요양보호사"
+
+ let nameStack = HStack([
+ nameLabel,
+ nameTitleLabel,
+ ], spacing: 2, alignment: .center)
+
+ let divider = UIView()
+ divider.backgroundColor = DSKitAsset.Colors.gray300.color
+ let detailWorkerInfoStack = HStack([
+ infoLabel,
+ divider,
+ expLabel,
+ ], spacing: 8)
+
+ NSLayoutConstraint.activate([
+ divider.widthAnchor.constraint(equalToConstant: 1),
+ divider.topAnchor.constraint(equalTo: detailWorkerInfoStack.topAnchor, constant: 3),
+ divider.bottomAnchor.constraint(equalTo: detailWorkerInfoStack.bottomAnchor, constant: -3),
+ ])
+
+ let labelStack = VStack([
+ workingTag,
+ nameStack,
+ detailWorkerInfoStack,
+ ], spacing: 2, alignment: .leading)
+
+ let workerInfoStack = HStack([
+ profileImageContainer,
+ labelStack,
+ Spacer(),
+// starButton
+ ], spacing: 16, alignment: .top)
+
+// NSLayoutConstraint.activate([
+// starButton.widthAnchor.constraint(equalToConstant: 22),
+// starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor),
+// ])
+
+ let buttonStack = HStack([
+ showProfileButton, employButton
+ ], spacing: 8, distribution: .fillEqually)
+
+ let mainStack = VStack([
+ workerInfoStack,
+ buttonStack
+ ], spacing: 12, alignment: .fill)
+
+ [
+ mainStack
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ self.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ mainStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor),
+ mainStack.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor),
+ mainStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor),
+ mainStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor),
+ ])
+ }
+
+ private func setObservable() {
+
+ }
+
+ public func bind(ro: ApplicantCardRO) {
+
+ if let imageUrl = ro.profileUrl {
+ workerProfileImage
+ .setImage(url: imageUrl)
+ }
+
+ workingTag.textString = ro.isJobFinding ? "구직중" : "휴식중"
+// starButton.setState(ro.isStared ? .accent : .normal)
+ nameLabel.textString = ro.name
+ infoLabel.textString = "\(ro.ageText) \(ro.genderText)"
+ expLabel.textString = "\(ro.expText)"
+ }
+}
+
+@available(iOS 17.0, *)
+#Preview("Preview", traits: .defaultLayout) {
+ let cardView = ApplicantCard()
+ cardView.bind(ro: .mock)
+ return cardView
+}
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift
new file mode 100644
index 00000000..c5af8cb9
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift
@@ -0,0 +1,93 @@
+//
+// ApplicantCardCell.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+
+import UIKit
+import RxSwift
+import RxCocoa
+import Entity
+
+public class ApplicantCardCell: UITableViewCell {
+
+ public static let identifier = String(describing: ApplicantCardCell.self)
+
+ var viewModel: ApplicantCardViewModelable?
+
+ let cardView = ApplicantCard()
+
+ private var disposables: [Disposable?]?
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ setAppearance()
+ setLayout()
+ }
+ public required init?(coder: NSCoder) { fatalError() }
+
+ public override func prepareForReuse() {
+ disposables?.forEach { $0?.dispose() }
+ disposables = nil
+ }
+
+ func setAppearance() { }
+
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+
+ contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0))
+ }
+
+ func setLayout() {
+
+ [
+ cardView
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ cardView.topAnchor.constraint(equalTo: contentView.topAnchor),
+ cardView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
+ cardView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
+ cardView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+ ])
+ }
+
+ public func bind(viewModel: ApplicantCardViewModelable) {
+
+ self.viewModel = viewModel
+
+ let disposables: [Disposable?] = [
+ // Output
+ viewModel
+ .renderObject?
+ .drive(onNext: { [cardView] ro in
+ cardView.bind(ro: ro)
+ }),
+
+ // Input
+// cardView
+// .starButton.eventPublisher
+// .map { state in
+// state == .accent
+// }
+// .bind(to: viewModel.staredThisWorker),
+
+ cardView
+ .showProfileButton.rx.tap
+ .bind(to: viewModel.showProfileButtonClicked),
+
+ cardView
+ .employButton.rx.tap
+ .bind(to: viewModel.employButtonClicked),
+ ]
+
+ self.disposables = disposables
+ }
+}
+
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift
new file mode 100644
index 00000000..5c604efe
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift
@@ -0,0 +1,87 @@
+//
+// WorkerInfoCardView.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import PresentationCore
+import RxCocoa
+import RxSwift
+import Entity
+
+public class PostInfoCardView: TappableUIView {
+
+ // View
+ let contentView = CenterEmployCardInfoView()
+
+ // Observable
+ private let disposeBag = DisposeBag()
+
+ public override init() {
+ super.init()
+
+ setAppearance()
+ setLayout()
+ setObservable()
+ }
+
+ public required init?(coder: NSCoder) { fatalError() }
+
+ private func setAppearance() {
+ self.backgroundColor = .white
+ self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor
+ self.layer.borderWidth = 1.0
+ self.layer.cornerRadius = 12
+ }
+
+ private func setLayout() {
+ self.layoutMargins = .init(
+ top: 16,
+ left: 16,
+ bottom: 16,
+ right: 16
+ )
+
+ let mainStack = VStack([
+ HStack([contentView, Spacer()])
+ ],alignment: .fill)
+
+ [
+ mainStack
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ self.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ mainStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor),
+ mainStack.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor),
+ mainStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor),
+ mainStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor),
+ ])
+ }
+
+ private func setObservable() { }
+
+ public func bind(vo: CenterEmployCardVO) {
+ let ro = CenterEmployCardRO.create(vo)
+ contentView.durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)"
+ contentView.informationLabel.textString = "\(ro.careGradeText) \(ro.ageText) \(ro.genderText)"
+ contentView.nameLabel.textString = ro.nameText
+ contentView.postTitleLabel.textString = ro.postTitle
+ }
+}
+
+@available(iOS 17.0, *)
+#Preview("Preview", traits: .defaultLayout) {
+
+ let view = PostInfoCardView()
+ view.contentView.durationLabel.textString = "2024. 07. 10 ~ 2024. 07. 31"
+ view.contentView.informationLabel.textString = "1등급 78세 여성"
+ view.contentView.nameLabel.textString = "홍길동"
+ view.contentView.postTitleLabel.textString = "서울특별시 강남구 신사동"
+
+ return view
+}
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift
similarity index 100%
rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCard.swift
rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift
similarity index 96%
rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCardCell.swift
rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift
index 93384959..4805f6ed 100644
--- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCardCell.swift
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift
@@ -12,6 +12,8 @@ import Entity
public class WorkerEmployCardCell: UITableViewCell {
+ public static let identifier = String(describing: WorkerEmployCardCell.self)
+
let tappableArea: TappableUIView = .init()
let cardView = WorkerEmployCard()
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/CenterCard/CenterInfoCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift
similarity index 100%
rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/CenterCard/CenterInfoCardView.swift
rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Navigation/IdleNavigationBar.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Navigation/IdleNavigationBar.swift
new file mode 100644
index 00000000..73cdefee
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Navigation/IdleNavigationBar.swift
@@ -0,0 +1,106 @@
+//
+// IdleNavigationBar.swift
+// DSKit
+//
+// Created by choijunios on 8/14/24.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+
+public class IdleNavigationBar: UIView {
+
+ // Init parameters
+
+ // View
+ public let backButton: UIButton = {
+
+ let btn = UIButton()
+
+ let image = DSKitAsset.Icons.back.image
+
+ let imageView = UIImageView(image: image)
+ btn.setImage(image, for: .normal)
+ btn.imageView?.contentMode = .scaleAspectFit
+
+ return btn
+ }()
+
+ public lazy var titleLabel: IdleLabel = {
+
+ let label = IdleLabel(typography: .Subtitle1)
+ label.textAlignment = .left
+ return label
+ }()
+
+ private let disposeBag = DisposeBag()
+
+ public init(
+ titleText: String = "",
+ innerViews: [UIView]
+ ) {
+ super.init(frame: .zero)
+
+ self.titleLabel.textString = titleText
+
+ setApearance()
+ setAutoLayout(innerViews: innerViews)
+ }
+
+ public required init(coder: NSCoder) { fatalError() }
+
+ func setApearance() {
+
+ }
+
+ private func setAutoLayout(innerViews: [UIView]) {
+
+ self.layoutMargins = .init(
+ top: 20.43,
+ left: 12,
+ bottom: 12,
+ right: 20
+ )
+
+ let mainStack = HStack(
+ [
+ [
+ backButton,
+ Spacer(width: 4),
+ titleLabel,
+ Spacer(),
+ ],
+ innerViews
+ ].flatMap { $0 },
+ alignment: .center,
+ distribution: .fill
+ )
+
+ [
+ mainStack
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ self.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ backButton.widthAnchor.constraint(equalToConstant: 32),
+ backButton.heightAnchor.constraint(equalToConstant: 32),
+
+ mainStack.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor),
+ mainStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor),
+ mainStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor),
+ mainStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor),
+ ])
+
+ }
+}
+
+@available(iOS 17.0, *)
+#Preview("Preview", traits: .defaultLayout) {
+ let innerView = Spacer(width: 50, height: 50)
+ innerView.backgroundColor = .red
+ let bar = IdleNavigationBar(titleText: "테스트", innerViews: [innerView])
+ return bar
+}
diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift
index b9275cc0..33bcba96 100644
--- a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift
+++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift
@@ -19,6 +19,9 @@ public class IdleTabBar: UIViewController {
public private(set) var viewControllers: [UIViewController] = []
private var tabBarItems: [IdleTabBarItem] = []
+ // View
+ var tabBarItemStack: UIView!
+
// 탭바 아이템
private var tabBarItemViews: [IdleTabBarItemViewable] = []
@@ -71,13 +74,15 @@ public class IdleTabBar: UIViewController {
tabBarItemViews = tabBarItems.map { item in
TextButtonType1(labelText: item.name)
}
-
+
let tabBarItemStack = HStack(
tabBarItemViews,
alignment: .fill,
distribution: .fillEqually
)
+ self.tabBarItemStack = tabBarItemStack
+
view.addSubview(tabBarItemStack)
tabBarItemStack.translatesAutoresizingMaskIntoConstraints = false
@@ -125,7 +130,7 @@ public class IdleTabBar: UIViewController {
currentView.topAnchor.constraint(equalTo: view.topAnchor),
currentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
currentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- currentView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
+ currentView.bottomAnchor.constraint(equalTo: tabBarItemStack.topAnchor)
])
}
diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleSecondaryButton.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleSecondaryButton.swift
index 310348b0..1f737799 100644
--- a/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleSecondaryButton.swift
+++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleSecondaryButton.swift
@@ -30,7 +30,7 @@ public enum IdleSecondaryButtonLevel {
var idleTextColor: UIColor {
switch self {
case .medium:
- DSKitAsset.Colors.orange500.color
+ DSKitAsset.Colors.gray300.color
}
}
@@ -52,14 +52,14 @@ public enum IdleSecondaryButtonLevel {
var idleBackgroundColor: UIColor {
switch self {
case .medium:
- .white
+ DSKitAsset.Colors.gray0.color
}
}
var accentBackgroundColor: UIColor {
switch self {
case .medium:
- DSKitAsset.Colors.orange100.color
+ DSKitAsset.Colors.gray0.color
}
}
@@ -74,14 +74,14 @@ public enum IdleSecondaryButtonLevel {
var idleBorderColor: UIColor {
switch self {
case .medium:
- DSKitAsset.Colors.orange400.color
+ DSKitAsset.Colors.gray200.color
}
}
var accentBorderColor: UIColor {
switch self {
case .medium:
- DSKitAsset.Colors.orange300.color
+ DSKitAsset.Colors.gray200.color
}
}
@@ -208,7 +208,7 @@ public class IdleSecondaryButton: TappableUIView {
let button = IdleSecondaryButton(level: .medium)
button.label.textString = "다음"
- button.setEnabled(false)
+ button.setEnabled(true)
return button
}
diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleThirdinaryButton.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleThirdinaryButton.swift
new file mode 100644
index 00000000..0cbb51f1
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleThirdinaryButton.swift
@@ -0,0 +1,214 @@
+//
+// IdleThirdinaryButton.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+
+public enum IdleThirdinaryButtonLevel {
+
+ case medium
+
+ var buttonHeight: CGFloat {
+ switch self {
+ case .medium:
+ 56
+ }
+ }
+
+ var typography: Typography {
+ switch self {
+ case .medium:
+ .Heading4
+ }
+ }
+ //textColor
+ var idleTextColor: UIColor {
+ switch self {
+ case .medium:
+ DSKitAsset.Colors.gray300.color
+ }
+ }
+
+ var accentTextColor: UIColor {
+ switch self {
+ case .medium:
+ DSKitAsset.Colors.gray300.color
+ }
+ }
+
+ var disabledTextColor: UIColor {
+ switch self {
+ case .medium:
+ DSKitAsset.Colors.gray300.color
+ }
+ }
+
+ //background
+ var idleBackgroundColor: UIColor {
+ switch self {
+ case .medium:
+ .white
+ }
+ }
+
+ var accentBackgroundColor: UIColor {
+ switch self {
+ case .medium:
+ DSKitAsset.Colors.orange100.color
+ }
+ }
+
+ var disabledBackgroundColor: UIColor {
+ switch self {
+ case .medium:
+ DSKitAsset.Colors.gray050.color
+ }
+ }
+
+ //border
+ var idleBorderColor: UIColor {
+ switch self {
+ case .medium:
+ DSKitAsset.Colors.orange400.color
+ }
+ }
+
+ var accentBorderColor: UIColor {
+ switch self {
+ case .medium:
+ DSKitAsset.Colors.orange300.color
+ }
+ }
+
+ var disabledBorderColor: UIColor {
+ switch self {
+ case .medium:
+ DSKitAsset.Colors.gray200.color
+ }
+ }
+}
+
+public class IdleThirdinaryButton: TappableUIView {
+
+ // State
+ public private(set) var isEnabled: Bool = true
+
+ // Init
+ public let level: IdleSecondaryButtonLevel
+
+ // View
+ public private(set) lazy var label: IdleLabel = {
+
+ let label = IdleLabel(typography: level.typography)
+ label.attrTextColor = .white
+ return label
+ }()
+
+ public override var intrinsicContentSize: CGSize {
+ .init(width: super.intrinsicContentSize.width, height: level.buttonHeight)
+ }
+
+ private let disposeBag = DisposeBag()
+
+ public init(
+ level: IdleSecondaryButtonLevel
+ ) {
+
+ self.level = level
+ super.init()
+
+ setApearance()
+ setAutoLayout()
+ setObservable()
+ }
+
+ required init?(coder: NSCoder) { fatalError() }
+
+ private func setApearance() {
+ self.layer.cornerRadius = 8
+ self.clipsToBounds = true
+ self.layer.borderWidth = 1.0
+
+ // InitialSetting
+ backgroundColor = level.idleBackgroundColor
+ label.attrTextColor = level.idleTextColor
+ layer.borderColor = level.idleBorderColor.cgColor
+ }
+
+ private func setAutoLayout() {
+
+ [
+ label
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ self.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ label.centerYAnchor.constraint(equalTo: self.centerYAnchor),
+ label.centerXAnchor.constraint(equalTo: self.centerXAnchor),
+ ])
+ }
+
+ private func setObservable() {
+
+ self.rx.tap
+ .subscribe { [weak self] _ in
+ guard let self else { return }
+
+ setToAccent()
+
+ UIView.animate(withDuration: 0.3) { [weak self] in
+ guard let self else { return }
+
+ setToIdle()
+ }
+ }
+ .disposed(by: disposeBag)
+ }
+
+ public func setEnabled(_ isEnabled: Bool) {
+ self.isEnabled = isEnabled
+ self.isUserInteractionEnabled = isEnabled
+
+ if isEnabled {
+ setToIdle()
+ }
+ else {
+ setToDisabled()
+ }
+ }
+
+ private func setToIdle() {
+ backgroundColor = level.idleBackgroundColor
+ label.attrTextColor = level.idleTextColor
+ layer.borderColor = level.idleBorderColor.cgColor
+ }
+
+ private func setToAccent() {
+ backgroundColor = level.accentBackgroundColor
+ label.attrTextColor = level.accentTextColor
+ layer.borderColor = level.accentBorderColor.cgColor
+ }
+
+ private func setToDisabled() {
+ backgroundColor = level.disabledBackgroundColor
+ label.attrTextColor = level.disabledTextColor
+ layer.borderColor = level.disabledBorderColor.cgColor
+ }
+}
+
+@available(iOS 17.0, *)
+#Preview("Preview", traits: .defaultLayout) {
+
+ let button = IdleSecondaryButton(level: .medium)
+ button.label.textString = "다음"
+ button.setEnabled(false)
+ return button
+}
+
diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/SingleImageButton.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/SingleImageButton.swift
new file mode 100644
index 00000000..30a63df1
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/SingleImageButton.swift
@@ -0,0 +1,46 @@
+//
+// SingleImageButton.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+
+public class SingleImageButton: TappableUIView {
+
+ let imageView: UIImageView = {
+ let view = UIImageView()
+ view.contentMode = .scaleAspectFit
+ return view
+ }()
+
+ let disposeBag = DisposeBag()
+
+ public override init() {
+
+ super.init()
+
+ NSLayoutConstraint.activate([
+ imageView.topAnchor.constraint(equalTo: self.topAnchor),
+ imageView.leftAnchor.constraint(equalTo: self.leftAnchor),
+ imageView.rightAnchor.constraint(equalTo: self.rightAnchor),
+ imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ ])
+
+ rx.tap
+ .subscribe { [weak self] _ in
+ self?.alpha = 0.5
+ UIView.animate(withDuration: 0.35) { [weak self] in
+ self?.alpha = 1.0
+ }
+ }
+ .disposed(by: disposeBag)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError()
+ }
+}
diff --git a/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift b/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift
index 1ffea61c..afac6eae 100644
--- a/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift
+++ b/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift
@@ -37,4 +37,13 @@ public extension UIImage {
view.contentMode = .scaleAspectFit
return view
}
+
+ /// SingleImageButton을 만듭니다.
+ func toButton(tintColor: UIColor) -> SingleImageButton {
+ self.withRenderingMode(.alwaysTemplate)
+ let btn = SingleImageButton()
+ btn.tintColor = tintColor
+ btn.imageView.image = self
+ return btn
+ }
}
diff --git a/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift b/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift
new file mode 100644
index 00000000..a7f3db0a
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift
@@ -0,0 +1,209 @@
+//
+// IdleTabControlBar.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import RxCocoa
+import RxSwift
+
+public protocol IdleTabItem {
+ associatedtype ID: Equatable
+ var id: ID { get }
+ var tabLabelText: String { get }
+}
+
+fileprivate class IdleTabBarCell: TappableUIView {
+
+ let label: IdleLabel = .init(typography: .Subtitle3)
+
+ override init() {
+
+ super.init()
+
+ self.addSubview(label)
+ label.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ label.centerYAnchor.constraint(equalTo: self.centerYAnchor),
+ label.centerXAnchor.constraint(equalTo: self.centerXAnchor),
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError()
+ }
+}
+
+public class IdleTabControlBar: UIView {
+
+ let items: [Item]
+
+ public let statePublisher: BehaviorRelay-
+
+ private var buttons: [IdleTabBarCell]!
+ let movingBar: UIView = {
+ let view = Spacer(height: 2)
+ view.backgroundColor = DSKitAsset.Colors.gray900.color
+ return view
+ }()
+
+ public private(set) var currentIndex: Int?
+
+ public override var intrinsicContentSize: CGSize {
+ .init(
+ width: super.intrinsicContentSize.width,
+ height: 48
+ )
+ }
+
+ let disposeBag = DisposeBag()
+
+ public init?(items: [Item], initialItem: Item) {
+
+ if items.isEmpty { return nil }
+
+ self.items = items
+ self.statePublisher = .init(value: initialItem)
+ super.init(frame: .zero)
+ setLayout()
+ selectItem(item: initialItem, animated: false)
+ setObservable()
+ }
+ public required init?(coder: NSCoder) { fatalError() }
+
+ private func setLayout() {
+
+ buttons = items.map { item in
+ let btn = IdleTabBarCell()
+ btn.label.textString = item.tabLabelText
+ return btn
+ }
+
+ let buttonStack = HStack(buttons, alignment: .fill, distribution: .fillEqually)
+
+
+ let barBackGroundView = Spacer(height: 1)
+ barBackGroundView.backgroundColor = DSKitAsset.Colors.gray100.color
+
+ [
+ buttonStack,
+ barBackGroundView,
+ movingBar,
+ ].enumerated().forEach { index, view in
+ view.layer.zPosition = CGFloat(index)
+ view.translatesAutoresizingMaskIntoConstraints = false
+ self.addSubview(view)
+ }
+
+ NSLayoutConstraint.activate([
+ buttonStack.topAnchor.constraint(equalTo: self.topAnchor),
+ buttonStack.leftAnchor.constraint(equalTo: self.leftAnchor),
+ buttonStack.rightAnchor.constraint(equalTo: self.rightAnchor),
+ buttonStack.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+
+ barBackGroundView.leftAnchor.constraint(equalTo: self.leftAnchor),
+ barBackGroundView.rightAnchor.constraint(equalTo: self.rightAnchor),
+ barBackGroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+
+ movingBar.leftAnchor.constraint(equalTo: self.leftAnchor),
+ movingBar.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ movingBar.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.0/CGFloat(items.count))
+ ])
+ }
+
+ private func setObservable() {
+
+ buttons
+ .enumerated()
+ .forEach { (index, tappableView) in
+ tappableView
+ .rx.tap
+ .map { [weak self] _ in
+ self?.items[index]
+ }
+ .compactMap { $0 }
+ .observe(on: MainScheduler.instance)
+ .subscribe(onNext: { [weak self] item in
+ self?.selectItem(item: item)
+ self?.statePublisher.accept(item)
+ })
+ .disposed(by: disposeBag)
+ }
+ }
+
+ public func selectItem(item: Item, animated: Bool = true) {
+
+ let moveToIndex = items.firstIndex(where: { $0.id == item.id })
+
+ if currentIndex == moveToIndex { return }
+
+ UIView.animate(withDuration: animated ? 0.2 : 0) { [weak self] in
+
+ guard let self else { return }
+
+ // 라벨색상변경
+ buttons
+ .enumerated()
+ .forEach { [weak self] (index, view) in
+
+ let isSelected = item.id == self?.items[index].id
+ view.label.attrTextColor = isSelected ? DSKitAsset.Colors.gray900.color : DSKitAsset.Colors.gray300.color
+ }
+
+ // 하단 바이동
+ currentIndex = moveToIndex
+ layoutSubviews()
+ }
+ }
+
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+
+ // 레이아웃 요청 이후, 레이아웃 적용 이후
+ movingBar.transform = .init(translationX: movingBar.bounds.width * CGFloat(currentIndex ?? 0), y: 0)
+ }
+}
+
+fileprivate enum TestTab {
+ case tab1
+ case tab2
+}
+
+
+fileprivate struct TestItem: IdleTabItem {
+ typealias ID = TestTab
+ var tabLabelText: String
+ var id: TestTab
+}
+
+@available(iOS 17.0, *)
+#Preview("Preview", traits: .defaultLayout) {
+
+ let item1 = TestItem(
+ tabLabelText: "진행 중인 공고",
+ id: .tab1
+ )
+ let item2 = TestItem(
+ tabLabelText: "이전 공고",
+ id: .tab2
+ )
+
+ let tabBar = IdleTabControlBar(
+ items: [item1, item2],
+ initialItem: item1
+ )!
+
+ let vc = UIViewController()
+ vc.view.addSubview(tabBar)
+ tabBar.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+ tabBar.leftAnchor.constraint(equalTo: vc.view.leftAnchor),
+ tabBar.rightAnchor.constraint(equalTo: vc.view.rightAnchor),
+ tabBar.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor),
+ ])
+
+ return vc
+}
diff --git a/project/Projects/Presentation/DSKit/Sources/UIImageView+KingFisher.swift b/project/Projects/Presentation/DSKit/Sources/UIImageView+KingFisher.swift
new file mode 100644
index 00000000..cd2e7327
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Sources/UIImageView+KingFisher.swift
@@ -0,0 +1,24 @@
+//
+// UIImageView+KingFisher.swift
+// DSKit
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import Kingfisher
+
+public extension UIImageView {
+
+ /// KingFisher를 사용해 이미지를 적용합니다.
+ /// option
+ /// - let pngSerializer = FormatIndicatedCacheSerializer.png
+ func setImage(url: URL) {
+ let pngSerializer = FormatIndicatedCacheSerializer.png
+ self
+ .kf.setImage(
+ with: url,
+ options: [.cacheSerializer(pngSerializer)]
+ )
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift
index c3e1686d..76918688 100644
--- a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift
+++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift
@@ -45,14 +45,14 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
navigationController: navigationController
)
- let vc = PostOverviewVC()
+ let vc = CenterRecruitmentPostBoardVC()
- vc.bind(viewModel: vm)
+// vc.bind(viewModel: vm)
window = UIWindow(windowScene: windowScene)
window?.rootViewController = vc
window?.makeKeyAndVisible()
- coordinator.start()
+// coordinator.start()
}
}
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift
new file mode 100644
index 00000000..bdb37ff8
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift
@@ -0,0 +1,221 @@
+//
+// CheckApplicantVC.swift
+// CenterFeature
+//
+// Created by choijunios on 8/13/24.
+//
+
+import UIKit
+import BaseFeature
+import PresentationCore
+import RxCocoa
+import RxSwift
+import Entity
+import DSKit
+
+class MyTableView: UITableView {
+
+ override var intrinsicContentSize: CGSize {
+
+ return CGSize(width: contentSize.width, height: contentSize.height+self.contentInset.top+self.contentInset.bottom)
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ invalidateIntrinsicContentSize()
+ }
+}
+
+public class CheckApplicantVC: BaseViewController {
+
+ typealias Cell = ApplicantCardCell
+
+ var viewModel: CheckApplicantViewModelable?
+
+ // Init
+
+ // View
+ let navigationBar: NavigationBarType1 = {
+ let view = NavigationBarType1(navigationTitle: "지원자 확인")
+ return view
+ }()
+ let postSummaryCard: PostInfoCardView = {
+ let view = PostInfoCardView()
+ return view
+ }()
+
+ let applicantTitleLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Subtitle2)
+ label.textString = "위 공고에 지원한 보호사 목록이에요."
+ return label
+ }()
+
+ let applicantTableView: MyTableView = {
+ let tableView = MyTableView()
+ return tableView
+ }()
+
+ // Observable
+ private let disposeBag = DisposeBag()
+
+ let postApplicantVO: BehaviorRelay<[PostApplicantVO]> = .init(value: [])
+
+ public init() {
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ public required init?(coder: NSCoder) { fatalError() }
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+ setAppearance()
+ setLayout()
+ setObservable()
+ setTableView()
+ }
+
+ private func setAppearance() {
+ view.backgroundColor = DSKitAsset.Colors.gray0.color
+ }
+
+ private func setLayout() {
+
+ let divider = Spacer(height: 8)
+ divider.backgroundColor = DSKitAsset.Colors.gray050.color
+
+
+ let contentView = UIView()
+ contentView.backgroundColor = DSKitAsset.Colors.gray0.color
+
+ [
+ postSummaryCard,
+
+ divider,
+
+ applicantTitleLabel,
+ applicantTableView,
+ ].forEach {
+ contentView.addSubview($0)
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ }
+
+ NSLayoutConstraint.activate([
+ postSummaryCard.topAnchor.constraint(equalTo: contentView.topAnchor),
+ postSummaryCard.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20),
+ postSummaryCard.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20),
+
+ divider.topAnchor.constraint(equalTo: postSummaryCard.bottomAnchor, constant: 20),
+ divider.leftAnchor.constraint(equalTo: contentView.leftAnchor),
+ divider.rightAnchor.constraint(equalTo: contentView.rightAnchor),
+
+ applicantTitleLabel.topAnchor.constraint(equalTo: divider.topAnchor, constant: 20),
+ applicantTitleLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20),
+
+ applicantTableView.topAnchor.constraint(equalTo: applicantTitleLabel.bottomAnchor, constant: 20),
+ applicantTableView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20),
+ applicantTableView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20),
+ applicantTableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+ ])
+
+ let scrollView = UIScrollView()
+ scrollView.delaysContentTouches = false
+ scrollView.contentInset = .init(
+ top: 36,
+ left: 0,
+ bottom: 20,
+ right: 0
+ )
+ let contentGuide = scrollView.contentLayoutGuide
+ let frameGuide = scrollView.frameLayoutGuide
+
+ scrollView.addSubview(contentView)
+ contentView.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+ contentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
+ contentView.leftAnchor.constraint(equalTo: contentGuide.leftAnchor),
+ contentView.rightAnchor.constraint(equalTo: contentGuide.rightAnchor),
+ contentView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor),
+
+ contentView.widthAnchor.constraint(equalTo: frameGuide.widthAnchor),
+ ])
+
+ [
+ navigationBar,
+ scrollView,
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21),
+ navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 12),
+
+ scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
+ scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
+ scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
+ scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ ])
+
+ }
+
+ private func setTableView() {
+ applicantTableView.rowHeight = UITableView.automaticDimension
+ applicantTableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier)
+ applicantTableView.isScrollEnabled = false
+ applicantTableView.dataSource = self
+ applicantTableView.delegate = self
+ applicantTableView.separatorStyle = .none
+ applicantTableView.delaysContentTouches = false
+ }
+
+ private func setObservable() { }
+
+ public func bind(viewModel: CheckApplicantViewModelable) {
+
+ self.viewModel = viewModel
+
+ navigationBar
+ .eventPublisher
+ .bind(to: viewModel.exitButtonClicked)
+ .disposed(by: disposeBag)
+
+ postSummaryCard
+ .bind(vo: viewModel.postCardVO)
+
+ viewModel
+ .postApplicantVO?
+ .drive(onNext: { [weak self] vo in
+ guard let self else { return }
+ postApplicantVO.accept(vo)
+ applicantTableView.reloadData()
+ })
+ .disposed(by: disposeBag)
+
+ rx.viewWillAppear
+ .map { _ in }
+ .bind(to: viewModel.requestpostApplicantVO)
+ .disposed(by: disposeBag)
+ }
+}
+
+extension CheckApplicantVC: UITableViewDataSource, UITableViewDelegate {
+
+ public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ postApplicantVO.value.count
+ }
+
+ public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell
+
+ if let viewModel = self.viewModel {
+ let vm = viewModel.createApplicantCardVM(vo: postApplicantVO.value[indexPath.row])
+ cell.bind(viewModel: vm)
+ cell.selectionStyle = .none
+ }
+
+ return cell
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift
new file mode 100644
index 00000000..e514744a
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift
@@ -0,0 +1,122 @@
+//
+// CheckApplicantVM.swift
+// CenterFeature
+//
+// Created by choijunios on 8/13/24.
+//
+
+import Foundation
+import RxSwift
+import RxCocoa
+import Entity
+import DSKit
+import PresentationCore
+
+public protocol CheckApplicantViewModelable {
+ // Input
+ var requestpostApplicantVO: PublishRelay { get }
+ var exitButtonClicked: PublishRelay { get }
+
+ // Output
+ var postApplicantVO: Driver<[PostApplicantVO]>? { get }
+ var postCardVO: CenterEmployCardVO { get }
+ var alert: Driver? { get }
+
+ func createApplicantCardVM(vo: PostApplicantVO) -> ApplicantCardVM
+}
+
+public class CheckApplicantVM: CheckApplicantViewModelable {
+
+ weak var coorindator: CheckApplicantCoordinatable?
+
+ public var exitButtonClicked: PublishRelay = .init()
+ public var requestpostApplicantVO: PublishRelay = .init()
+ public var postCardVO: CenterEmployCardVO
+
+ public var postApplicantVO: Driver<[PostApplicantVO]>?
+ public var alert: RxCocoa.Driver?
+
+ let disposeBag = DisposeBag()
+
+ public init(postCardVO: CenterEmployCardVO, coorindator: CheckApplicantCoordinatable?) {
+ self.postCardVO = postCardVO
+ self.coorindator = coorindator
+
+ exitButtonClicked
+ .subscribe(onNext: { [weak self] _ in
+
+ self?.coorindator?.taskFinished()
+ })
+ .disposed(by: disposeBag)
+
+ // Input
+ let requestPostApplicantVOResult = requestpostApplicantVO
+ .flatMap { [unowned self] _ in
+ publishPostApplicantVOMocks()
+ }
+ .share()
+
+ let requestPostApplicantSuccess = requestPostApplicantVOResult.compactMap { $0.value }
+ let requestPostApplicantFailure = requestPostApplicantVOResult.compactMap { $0.error }
+
+ // Output
+ postApplicantVO = requestPostApplicantSuccess.asDriver(onErrorJustReturn: [])
+
+ alert = requestPostApplicantFailure
+ .map { error in
+
+ DefaultAlertContentVO(
+ title: "시스템 오류",
+ message: error.message
+ )
+ }
+ .asDriver(onErrorJustReturn: .default)
+ }
+
+ public func createApplicantCardVM(vo: PostApplicantVO) -> ApplicantCardVM {
+ .init(vo: vo, coordinator: coorindator)
+ }
+
+ func publishPostApplicantVOMocks() -> Single> {
+
+ .just(.success((0...10).map { _ in PostApplicantVO.mock }))
+ }
+}
+
+
+// MARK: ApplicantCardVM
+public class ApplicantCardVM: ApplicantCardViewModelable {
+
+ // Init
+ let id: String
+ weak var coordinator: CheckApplicantCoordinatable?
+
+ public var showProfileButtonClicked: PublishRelay = .init()
+ public var employButtonClicked: PublishRelay = .init()
+ public var staredThisWorker: PublishRelay = .init()
+
+ public var renderObject: Driver?
+
+ let disposeBag = DisposeBag()
+
+ public init(vo: PostApplicantVO, coordinator: CheckApplicantCoordinatable?) {
+ self.id = vo.workerId
+ self.coordinator = coordinator
+
+ // MARK: RenderObject
+ let publishRelay: BehaviorRelay = .init(value: .mock)
+ renderObject = publishRelay.asDriver(onErrorJustReturn: .mock)
+
+ publishRelay.accept(ApplicantCardRO.create(vo: vo))
+
+ // MARK: 버튼 처리
+ showProfileButtonClicked
+ .subscribe(onNext: { [weak self] _ in
+ guard let self else { return }
+ coordinator?.showWorkerProfileScreen(
+ profileId: id
+ )
+ })
+ .disposed(by: disposeBag)
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift
new file mode 100644
index 00000000..6cd334b8
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift
@@ -0,0 +1,182 @@
+//
+// CenterRecruitmentPostBoardVC.swift
+// CenterFeature
+//
+// Created by choijunios on 8/12/24.
+//
+
+import UIKit
+import BaseFeature
+import PresentationCore
+import RxCocoa
+import RxSwift
+import Entity
+import DSKit
+
+public class CenterRecruitmentPostBoardVC: BaseViewController {
+ enum TabBarState: Int, CaseIterable {
+ case onGoingPost = 0
+ case closedPost = 1
+
+ var titleText: String {
+ switch self {
+ case .onGoingPost:
+ "진행 중인 공고"
+ case .closedPost:
+ "이전 공고"
+ }
+ }
+ }
+ struct TabBarItem: IdleTabItem {
+ var id: TabBarState
+ var tabLabelText: String
+
+ init(id: TabBarState) {
+ self.id = id
+ self.tabLabelText = id.titleText
+ }
+ }
+
+ var viewModel: CenterRecruitmentPostBoardViewModelable?
+
+ private var currentState: TabBarState = .onGoingPost
+ private let viewControllerDict: [TabBarState: UIViewController] = [
+ .onGoingPost : OnGoingPostVC(),
+ .closedPost : ClosedPostVC()
+ ]
+
+ // Init
+
+ // View
+ let titleLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Heading1)
+ label.textString = "공고 관리"
+ return label
+ }()
+
+ lazy var tabBar: IdleTabControlBar = .init(
+ items: TabBarState.allCases.map { TabBarItem(id: $0) },
+ initialItem: .init(id: currentState)
+ )!
+
+ // Observable
+ private let disposeBag = DisposeBag()
+
+ public init() {
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ public required init?(coder: NSCoder) { fatalError() }
+
+ public override func viewDidLoad() {
+ setAppearance()
+ setLayout()
+ setObservable()
+
+ addViewControllerAndSetLayout(vc: viewControllerDict[currentState]!)
+ }
+
+ private func setAppearance() {
+ view.backgroundColor = DSKitAsset.Colors.gray0.color
+ }
+
+ private func setLayout() {
+ [
+ titleLabel,
+ tabBar,
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21),
+ titleLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20),
+
+ tabBar.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
+ tabBar.leftAnchor.constraint(equalTo: view.leftAnchor),
+ tabBar.rightAnchor.constraint(equalTo: view.rightAnchor),
+ ])
+ }
+
+ private func setObservable() {
+ tabBar
+ .statePublisher
+ .subscribe(onNext: { [weak self] item in
+ self?.showViewController(state: item.id)
+ })
+ .disposed(by: disposeBag)
+ }
+
+ private func showViewController(state: TabBarState) {
+
+ if currentState == state { return }
+
+ // 탭바터치 정지
+ tabBar.isUserInteractionEnabled = false
+
+ /// viewWillAppear이후에 호출
+ let prevViewController = viewControllerDict[currentState]
+ let vc = viewControllerDict[state]!
+
+ let prevIndex = currentState.rawValue
+ let currentIndex = state.rawValue
+
+ addViewControllerAndSetLayout(vc: vc)
+
+ vc.view.transform = .init(translationX: view.bounds.width * (prevIndex < currentIndex ? 1 : -1), y: 0)
+
+ UIView.animate(withDuration: 0.2) { [weak self] in
+
+ guard let self else { return }
+
+ vc.view.transform = .identity
+ prevViewController?.view.transform = .init(translationX: (prevIndex < currentIndex ? -1 : 1) * view.bounds.width, y: 0)
+
+ } completion: { [weak self] _ in
+
+ prevViewController?.view.removeFromSuperview()
+
+ prevViewController?.willMove(toParent: nil)
+ prevViewController?.removeFromParent()
+
+ self?.currentState = state
+ self?.tabBar.isUserInteractionEnabled = true
+ }
+ }
+
+ private func addViewControllerAndSetLayout(vc: UIViewController) {
+ addChild(vc)
+ view.addSubview(vc.view)
+ vc.didMove(toParent: self)
+ vc.view.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+ vc.view.topAnchor.constraint(equalTo: tabBar.bottomAnchor),
+ vc.view.leftAnchor.constraint(equalTo: view.leftAnchor),
+ vc.view.rightAnchor.constraint(equalTo: view.rightAnchor),
+ vc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+ ])
+ }
+
+ public func bind(viewModel: CenterRecruitmentPostBoardViewModelable) {
+
+ self.viewModel = viewModel
+
+ (viewControllerDict[.onGoingPost] as? OnGoingPostVC)?.bind(viewModel: viewModel)
+ (viewControllerDict[.closedPost] as? ClosedPostVC)?.bind(viewModel: viewModel)
+
+ viewModel
+ .alert?
+ .drive(onNext: { [weak self] alertVO in
+ self?.showAlert(vo: alertVO)
+ })
+ .disposed(by: disposeBag)
+ }
+}
+
+@available(iOS 17.0, *)
+#Preview("Preview", traits: .defaultLayout) {
+
+ CenterRecruitmentPostBoardVC()
+}
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVM.swift
new file mode 100644
index 00000000..1587474d
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVM.swift
@@ -0,0 +1,124 @@
+//
+// CenterRecruitmentPostBoardVM.swift
+// CenterFeature
+//
+// Created by choijunios on 8/13/24.
+//
+
+import Foundation
+import UIKit
+import BaseFeature
+import PresentationCore
+import RxCocoa
+import RxSwift
+import Entity
+import DSKit
+
+public protocol CenterRecruitmentPostBoardViewModelable: OnGoingPostViewModelable & ClosedPostViewModelable {
+ var alert: Driver? { get }
+}
+
+
+public class CenterRecruitmentPostBoardVM: CenterRecruitmentPostBoardViewModelable {
+
+ weak var coordinator: RecruitmentManagementCoordinatable?
+
+ public var requestOngoingPost: PublishRelay = .init()
+ public var requestClosedPost: PublishRelay = .init()
+
+ public var ongoingPostCardVO: Driver<[CenterEmployCardVO]>?
+ public var closedPostCardVO: Driver<[CenterEmployCardVO]>?
+
+ public var alert: Driver?
+
+ public init(coordinator: RecruitmentManagementCoordinatable?) {
+ self.coordinator = coordinator
+
+ let requestOngoingPostResult = requestOngoingPost
+ .flatMap { [unowned self] _ in
+ publishOngoingPostMocks()
+ }
+ .share()
+
+ let requestOngoingPostSuccess = requestOngoingPostResult.compactMap { $0.value }
+ let requestOngoingPostFailure = requestOngoingPostResult.compactMap { $0.error }
+
+ ongoingPostCardVO = requestOngoingPostSuccess.asDriver(onErrorJustReturn: [])
+
+
+ let requestClosedPostResult = requestClosedPost
+ .flatMap { [unowned self] _ in
+ publishClosedPostMocks()
+ }
+ .share()
+
+ let requestClosedPostSuccess = requestClosedPostResult.compactMap { $0.value }
+ let requestClosedPostFailure = requestClosedPostResult.compactMap { $0.error }
+
+ closedPostCardVO = requestClosedPostSuccess.asDriver(onErrorJustReturn: [])
+
+ alert = Observable.merge(
+ requestOngoingPostFailure,
+ requestClosedPostFailure
+ ).map { error in
+ DefaultAlertContentVO(
+ title: "시스템 오류",
+ message: error.message
+ )
+ }
+ .asDriver(onErrorJustReturn: .default)
+ }
+
+ func publishOngoingPostMocks() -> Single> {
+ return .just(.success((0...10).map { _ in CenterEmployCardVO.mock }))
+ }
+
+ func publishClosedPostMocks() -> Single> {
+ return .just(.success((0...10).map { _ in CenterEmployCardVO.mock }))
+ }
+
+ public func createCellVM(vo: CenterEmployCardVO) -> any CenterEmployCardViewModelable {
+ CenterEmployCardVM(
+ vo: vo,
+ coordinator: coordinator
+ )
+ }
+}
+
+// MARK: 카드 뷰에 사용될 ViewModel
+class CenterEmployCardVM: CenterEmployCardViewModelable {
+
+ weak var coordinator: RecruitmentManagementCoordinatable?
+
+ // Init
+ let id: String
+
+ // Output
+ var renderObject: Driver?
+
+ // Input
+ var cardClicked: PublishRelay = .init()
+ var checkApplicantBtnClicked: PublishRelay = .init()
+ var editPostBtnClicked: PublishRelay = .init()
+ var terminatePostBtnClicked: PublishRelay = .init()
+
+ let disposeBag = DisposeBag()
+
+ init(vo: CenterEmployCardVO, coordinator: RecruitmentManagementCoordinatable? = nil) {
+ self.id = vo.postId
+ self.coordinator = coordinator
+
+ // MARK: RenderObject
+ let publishRelay: BehaviorRelay = .init(value: .mock)
+ renderObject = publishRelay.asDriver(onErrorJustReturn: .mock)
+
+ publishRelay.accept(CenterEmployCardRO.create(vo))
+
+ // MARK: 버튼 처리
+ checkApplicantBtnClicked
+ .subscribe(onNext: { [weak self] _ in
+ self?.coordinator?.showCheckingApplicantScreen(vo)
+ })
+ .disposed(by: disposeBag)
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift
new file mode 100644
index 00000000..031381bb
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift
@@ -0,0 +1,137 @@
+//
+// ClosedPostVC.swift
+// CenterFeature
+//
+// Created by choijunios on 8/13/24.
+//
+
+import UIKit
+import BaseFeature
+import PresentationCore
+import RxCocoa
+import RxSwift
+import Entity
+import DSKit
+
+public protocol ClosedPostViewModelable {
+
+ var closedPostCardVO: Driver<[CenterEmployCardVO]>? { get }
+ var requestClosedPost: PublishRelay { get }
+
+ func createCellVM(vo: CenterEmployCardVO) -> CenterEmployCardViewModelable
+}
+
+public class ClosedPostVC: BaseViewController {
+
+ typealias Cell = CenterEmployCardCell
+
+ var viewModel: ClosedPostViewModelable?
+
+ // View
+ let postTableView: UITableView = {
+ let tableView = UITableView()
+ tableView.rowHeight = UITableView.automaticDimension
+ tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier)
+ return tableView
+ }()
+
+ let tableHeader = BoardSortigHeaderView()
+
+ // Observable
+ private let disposeBag = DisposeBag()
+
+ let closedPostCardVO: BehaviorRelay<[CenterEmployCardVO]> = .init(value: [])
+
+ public init() {
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ public required init?(coder: NSCoder) { fatalError() }
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+ setAppearance()
+ setLayout()
+ setObservable()
+ setTableView()
+ }
+
+ private func setTableView() {
+ postTableView.dataSource = self
+ postTableView.delegate = self
+ postTableView.separatorStyle = .none
+ postTableView.delaysContentTouches = false
+
+ postTableView.tableHeaderView = tableHeader
+
+ tableHeader.frame = .init(origin: .zero, size: .init(
+ width: view.bounds.width,
+ height: 60)
+ )
+ }
+
+ private func setAppearance() {
+
+ }
+
+ private func setLayout() {
+ [
+ postTableView
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ postTableView.topAnchor.constraint(equalTo: view.topAnchor),
+ postTableView.leftAnchor.constraint(equalTo: view.leftAnchor),
+ postTableView.rightAnchor.constraint(equalTo: view.rightAnchor),
+ postTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ ])
+ }
+
+ private func setObservable() {
+
+ }
+
+ public func bind(viewModel: ClosedPostViewModelable) {
+
+ self.viewModel = viewModel
+
+ // Output
+ viewModel
+ .closedPostCardVO?
+ .drive(onNext: { [weak self] vos in
+ guard let self else { return }
+ closedPostCardVO.accept(vos)
+ postTableView.reloadData()
+ })
+ .disposed(by: disposeBag)
+
+ // Input
+ rx.viewWillAppear
+ .map { _ in }
+ .bind(to: viewModel.requestClosedPost)
+ .disposed(by: disposeBag)
+ }
+}
+
+extension ClosedPostVC: UITableViewDataSource, UITableViewDelegate {
+
+ public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ closedPostCardVO.value.count
+ }
+
+ public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell
+ cell.selectionStyle = .none
+
+ if let viewModel = self.viewModel {
+ let vo = closedPostCardVO.value[indexPath.row]
+ let vm = viewModel.createCellVM(vo: vo)
+ cell.bind(viewModel: vm)
+ }
+ return cell
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift
new file mode 100644
index 00000000..1f8bcce9
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift
@@ -0,0 +1,176 @@
+//
+// OnGoingPostVC.swift
+// CenterFeature
+//
+// Created by choijunios on 8/13/24.
+//
+
+import UIKit
+import BaseFeature
+import PresentationCore
+import RxCocoa
+import RxSwift
+import Entity
+import DSKit
+
+public protocol OnGoingPostViewModelable {
+
+ var ongoingPostCardVO: Driver<[CenterEmployCardVO]>? { get }
+ var requestOngoingPost: PublishRelay { get }
+
+ func createCellVM(vo: CenterEmployCardVO) -> CenterEmployCardViewModelable
+}
+
+public class OnGoingPostVC: BaseViewController {
+
+ typealias Cell = CenterEmployCardCell
+
+ var viewModel: OnGoingPostViewModelable?
+
+ // View
+ let postTableView: UITableView = {
+ let tableView = UITableView()
+ tableView.rowHeight = UITableView.automaticDimension
+ tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier)
+ return tableView
+ }()
+
+ let tableHeader = BoardSortigHeaderView()
+
+ let ongoingPostCardVO: BehaviorRelay<[CenterEmployCardVO]> = .init(value: [.mock])
+
+ // Observable
+ private let disposeBag = DisposeBag()
+
+ public init() {
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ public required init?(coder: NSCoder) { fatalError() }
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+ setAppearance()
+ setLayout()
+ setObservable()
+ setTableView()
+ }
+
+ private func setTableView() {
+ postTableView.dataSource = self
+ postTableView.delegate = self
+ postTableView.separatorStyle = .none
+ postTableView.delaysContentTouches = false
+
+ postTableView.tableHeaderView = tableHeader
+
+ tableHeader.frame = .init(origin: .zero, size: .init(
+ width: view.bounds.width,
+ height: 60)
+ )
+ }
+
+ private func setAppearance() {
+
+ }
+
+ private func setLayout() {
+
+ [
+ postTableView
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ postTableView.topAnchor.constraint(equalTo: view.topAnchor),
+ postTableView.leftAnchor.constraint(equalTo: view.leftAnchor),
+ postTableView.rightAnchor.constraint(equalTo: view.rightAnchor),
+ postTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ ])
+
+ }
+
+ private func setObservable() {
+
+ }
+
+ public func bind(viewModel: OnGoingPostViewModelable) {
+
+ self.viewModel = viewModel
+
+ // Output
+ viewModel
+ .ongoingPostCardVO?
+ .drive(onNext: { [weak self] vos in
+ guard let self else { return }
+ ongoingPostCardVO.accept(vos)
+ postTableView.reloadData()
+ })
+ .disposed(by: disposeBag)
+
+ // Input
+ rx.viewWillAppear
+ .map { _ in }
+ .bind(to: viewModel.requestOngoingPost)
+ .disposed(by: disposeBag)
+ }
+}
+
+extension OnGoingPostVC: UITableViewDataSource, UITableViewDelegate {
+
+ public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ ongoingPostCardVO.value.count
+ }
+
+ public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell
+ cell.selectionStyle = .none
+
+ if let viewModel = self.viewModel {
+ let vo = ongoingPostCardVO.value[indexPath.row]
+ let vm = viewModel.createCellVM(vo: vo)
+ cell.bind(viewModel: vm)
+ }
+
+ return cell
+ }
+}
+
+class BoardSortigHeaderView: UIView {
+
+ let sortingTypeButton: ImageTextButton = {
+ let button = ImageTextButton(
+ iconImage: DSKitAsset.Icons.chevronDown.image,
+ position: .postfix
+ )
+ button.label.textString = "정렬 기준"
+ button.label.attrTextColor = DSKitAsset.Colors.gray300.color
+ return button
+ }()
+
+ init() {
+ super.init(frame: .zero)
+ setLayout()
+ }
+
+ required init?(coder: NSCoder) { fatalError() }
+
+ func setLayout() {
+
+ [
+ sortingTypeButton
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ self.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+ sortingTypeButton.topAnchor.constraint(equalTo: self.topAnchor, constant: 24),
+ sortingTypeButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -24),
+ sortingTypeButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12),
+ ])
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift
index 68bb72b7..eb81b020 100644
--- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift
+++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift
@@ -313,7 +313,7 @@ public class PostOverviewVC: BaseViewController {
.disposed(by: disposeBag)
- // 앞전가지 입력한 정보를 저장합니다.
+ // 앞전까지 입력한 정보를 저장합니다.
viewModel.updateToState()
// 화면이 등장할 때마다 유효한 상태를 불러옵니다.
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/ApplocationDetail/ApplicationDetailView.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/ApplocationDetail/ApplicationDetailView.swift
similarity index 100%
rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/ApplocationDetail/ApplicationDetailView.swift
rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/ApplocationDetail/ApplicationDetailView.swift
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/CustomerInformation/CustomerInformationView.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/CustomerInformation/CustomerInformationView.swift
similarity index 100%
rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/CustomerInformation/CustomerInformationView.swift
rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/CustomerInformation/CustomerInformationView.swift
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/CustomerRequirement/CustomerRequirementView.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/CustomerRequirement/CustomerRequirementView.swift
similarity index 100%
rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/CustomerRequirement/CustomerRequirementView.swift
rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/CustomerRequirement/CustomerRequirementView.swift
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/EditPost/EditPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/EditPost/EditPostVC.swift
similarity index 100%
rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/EditPost/EditPostVC.swift
rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/EditPost/EditPostVC.swift
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/RegisterCompleteScreen/RegisterCompleteVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/RegisterCompleteScreen/RegisterCompleteVC.swift
similarity index 100%
rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/RegisterCompleteScreen/RegisterCompleteVC.swift
rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/RegisterCompleteScreen/RegisterCompleteVC.swift
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/RegisterRecruitmentPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/RegisterRecruitmentPostVC.swift
similarity index 100%
rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/RegisterRecruitmentPostVC.swift
rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/RegisterRecruitmentPostVC.swift
diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/WorkTimeAndPay/WorkTimeAndPayView.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/WorkTimeAndPay/WorkTimeAndPayView.swift
similarity index 100%
rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/WorkTimeAndPay/WorkTimeAndPayView.swift
rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/WorkTimeAndPay/WorkTimeAndPayView.swift
diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift
new file mode 100644
index 00000000..e1cde0fa
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift
@@ -0,0 +1,81 @@
+//
+// CheckApplicantCoordinator.swift
+// RootFeature
+//
+// Created by choijunios on 8/13/24.
+//
+
+import UIKit
+import PresentationCore
+import UseCaseInterface
+import Entity
+import CenterFeature
+import WorkerFeature
+import BaseFeature
+
+public class CheckApplicantCoordinator: CheckApplicantCoordinatable {
+
+ public var childCoordinators: [any PresentationCore.Coordinator] = []
+
+ public struct Dependency {
+ let navigationController: UINavigationController
+ let centerEmployCardVO: CenterEmployCardVO
+ let workerProfileUseCase: WorkerProfileUseCase
+
+ public init(navigationController: UINavigationController, centerEmployCardVO: CenterEmployCardVO, workerProfileUseCase: WorkerProfileUseCase) {
+ self.navigationController = navigationController
+ self.centerEmployCardVO = centerEmployCardVO
+ self.workerProfileUseCase = workerProfileUseCase
+ }
+ }
+
+ public weak var viewControllerRef: UIViewController?
+ public weak var parent: ParentCoordinator?
+
+ public let navigationController: UINavigationController
+ let centerEmployCardVO: CenterEmployCardVO
+ let workerProfileUseCase: WorkerProfileUseCase
+
+ public init(
+ dependency: Dependency
+ ) {
+ self.navigationController = dependency.navigationController
+ self.centerEmployCardVO = dependency.centerEmployCardVO
+ self.workerProfileUseCase = dependency.workerProfileUseCase
+ }
+
+ deinit {
+ printIfDebug("\(String(describing: RegisterRecruitmentCoordinator.self))")
+ }
+
+ public func start() {
+ let vc = CheckApplicantVC()
+ let vm = CheckApplicantVM(
+ postCardVO: centerEmployCardVO,
+ coorindator: self
+ )
+ vc.bind(viewModel: vm)
+ viewControllerRef = vc
+ navigationController.pushViewController(vc, animated: true)
+ }
+}
+
+extension CheckApplicantCoordinator {
+
+ public func taskFinished() {
+ popViewController()
+ parent?.removeChildCoordinator(self)
+ }
+
+ public func showWorkerProfileScreen(profileId: String) {
+ let coordinator = WorkerProfileCoordinator(
+ dependency: .init(
+ profileMode: .otherProfile(id: profileId),
+ navigationController: navigationController,
+ workerProfileUseCase: workerProfileUseCase
+ )
+ )
+ addChildCoordinator(coordinator)
+ coordinator.start()
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift
index 0e0b7820..57671e8a 100644
--- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift
+++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift
@@ -6,9 +6,27 @@
//
import UIKit
+import CenterFeature
import PresentationCore
+import UseCaseInterface
+import Entity
-public class RecruitmentManagementCoordinator: ChildCoordinator {
+
+public class RecruitmentManagementCoordinator: RecruitmentManagementCoordinatable {
+
+ public struct Dependency {
+ weak var parent: CenterMainCoordinatable?
+ let navigationController: UINavigationController
+ let workerProfileUseCase: WorkerProfileUseCase
+
+ public init(parent: CenterMainCoordinatable? = nil, navigationController: UINavigationController, workerProfileUseCase: WorkerProfileUseCase) {
+ self.parent = parent
+ self.navigationController = navigationController
+ self.workerProfileUseCase = workerProfileUseCase
+ }
+ }
+
+ public var childCoordinators: [any PresentationCore.Coordinator] = []
public weak var viewControllerRef: UIViewController?
@@ -16,31 +34,42 @@ public class RecruitmentManagementCoordinator: ChildCoordinator {
public weak var parent: CenterMainCoordinatable?
+ let workerProfileUseCase: WorkerProfileUseCase
+
public init(
- parent: CenterMainCoordinatable,
- navigationController: UINavigationController
+ dependency: Dependency
) {
- self.parent = parent
- self.navigationController = navigationController
+ self.parent = dependency.parent
+ self.navigationController = dependency.navigationController
+ self.workerProfileUseCase = dependency.workerProfileUseCase
}
public func start() {
- let vc = RecuitmentManagementVC(coordinator: self)
+ let vc = CenterRecruitmentPostBoardVC()
+ let vm = CenterRecruitmentPostBoardVM(coordinator: self)
+ vc.bind(viewModel: vm)
+ viewControllerRef = vc
navigationController.pushViewController(vc, animated: false)
}
public func coordinatorDidFinish() {
-
+ popViewController()
+ parent?.removeChildCoordinator(self)
}
}
-extension RecruitmentManagementCoordinator {
-
- func showCenterRegisterScreen() {
- parent?.centerProfileRegister()
- }
-
- func showRegisterRecruitmentPostScreen() {
- parent?.registerRecruitmentPost()
+public extension RecruitmentManagementCoordinator {
+
+ func showCheckingApplicantScreen(_ centerEmployCardVO: CenterEmployCardVO) {
+ let coordinator = CheckApplicantCoordinator(
+ dependency: .init(
+ navigationController: navigationController,
+ centerEmployCardVO: centerEmployCardVO,
+ workerProfileUseCase: workerProfileUseCase
+ )
+ )
+ addChildCoordinator(coordinator)
+ coordinator.parent = self
+ coordinator.start()
}
}
diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/View/RecuitmentManagementVC.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/View/RecuitmentManagementVC.swift
index b296948a..4532a107 100644
--- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/View/RecuitmentManagementVC.swift
+++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/View/RecuitmentManagementVC.swift
@@ -41,17 +41,6 @@ public class RecuitmentManagementVC: UIViewController {
button2.setTitleColor(.black, for: .normal)
button2.isUserInteractionEnabled = true
- button1.rx.tap
- .subscribe { [weak coordinator] _ in
- coordinator?.showCenterRegisterScreen()
- }
- .disposed(by: dispoesBag)
-
- button2.rx.tap
- .subscribe { [weak coordinator] _ in
- coordinator?.showRegisterRecruitmentPostScreen()
- }
- .disposed(by: dispoesBag)
[
label,
button1,
diff --git a/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift
index 642cc560..f4d784fc 100644
--- a/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift
+++ b/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift
@@ -32,7 +32,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
repository: DefaultUserProfileRepository(store)
)
- let vm = WorkerMyProfileViewModel(workerProfileUseCase: useCase)
+ let vm = WorkerMyProfileViewModel(
+ coordinator: nil,
+ workerProfileUseCase: useCase
+ )
let vc = WorkerProfileViewController()
diff --git a/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift
new file mode 100644
index 00000000..0c775251
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift
@@ -0,0 +1,73 @@
+//
+// WorkerProfileCoordinator.swift
+// WorkerFeature
+//
+// Created by choijunios on 8/14/24.
+//
+
+import UIKit
+import PresentationCore
+import UseCaseInterface
+import Entity
+
+
+public class WorkerProfileCoordinator: ChildCoordinator {
+
+ public struct Dependency {
+ public let profileMode: ProfileMode
+ public let navigationController: UINavigationController
+ public let workerProfileUseCase: WorkerProfileUseCase
+
+ public init(profileMode: ProfileMode, navigationController: UINavigationController, workerProfileUseCase: WorkerProfileUseCase) {
+ self.profileMode = profileMode
+ self.navigationController = navigationController
+ self.workerProfileUseCase = workerProfileUseCase
+ }
+ }
+
+ public weak var viewControllerRef: UIViewController?
+ public weak var parent: ParentCoordinator?
+
+ public let navigationController: UINavigationController
+ let profileMode: ProfileMode
+ let workerProfileUseCase: WorkerProfileUseCase
+
+ public init(
+ dependency: Dependency
+ ) {
+ self.navigationController = dependency.navigationController
+ self.profileMode = dependency.profileMode
+ self.workerProfileUseCase = dependency.workerProfileUseCase
+ }
+
+ deinit {
+ printIfDebug("\(String(describing: WorkerProfileCoordinator.self))")
+ }
+
+ public func start() {
+ let vc = WorkerProfileViewController()
+
+ switch profileMode {
+ case .myProfile:
+ let vm = WorkerMyProfileViewModel(
+ coordinator: self,
+ workerProfileUseCase: workerProfileUseCase
+ )
+ vc.bind(vm)
+ case .otherProfile(let id):
+ let vm = WorkerProfileViewModel(
+ coordinator: self,
+ workerProfileUseCase: workerProfileUseCase,
+ workerId: id
+ )
+ vc.bind(vm)
+ }
+ viewControllerRef = vc
+ navigationController.pushViewController(vc, animated: true)
+ }
+
+ public func coordinatorDidFinish() {
+ popViewController(animated: true)
+ parent?.removeChildCoordinator(self)
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift
similarity index 93%
rename from project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift
rename to project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift
index 3467c0c9..c7dcc7a0 100644
--- a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift
+++ b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift
@@ -12,21 +12,6 @@ import RxCocoa
import DSKit
import Entity
import BaseFeature
-import Kingfisher
-
-protocol WorkerProfileEditViewModelable: WorkerProfileViewModelable {
-
- var requestUpload: PublishRelay { get }
- var editingImage: PublishRelay { get }
- var editingIsJobFinding: PublishRelay { get }
- var editingExpYear: PublishRelay { get }
- var editingAddress: PublishRelay { get }
- var editingIntroduce: PublishRelay { get }
- var editingSpecialty: PublishRelay { get }
-
- var uploadSuccess: Driver? { get }
- var alert: Driver? { get }
-}
public class EditWorkerProfileViewController: BaseViewController {
@@ -45,23 +30,14 @@ public class EditWorkerProfileViewController: BaseViewController {
}()
// 프로필 이미지
- let profileImageContainer: UIView = {
+ let profileImageContainer: UIImageView = {
- let view = UIView()
+ let view = UIImageView()
view.backgroundColor = DSKitAsset.Colors.orange100.color
view.layer.cornerRadius = 48
view.clipsToBounds = true
-
- /// PlaceHolderImage
- let imageView = DSKitAsset.Icons.workerProfilePlaceholder.image.toView()
- view.addSubview(imageView)
- imageView.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 26),
- imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25),
- imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -29),
- imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -27),
- ])
+ view.image = DSKitAsset.Icons.workerProfilePlaceholder.image
+ view.contentMode = .scaleAspectFit
return view
}()
diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift
similarity index 65%
rename from project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift
rename to project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift
index 81489cbb..2572f87e 100644
--- a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift
+++ b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift
@@ -12,36 +12,14 @@ import RxCocoa
import DSKit
import Entity
import BaseFeature
-import Kingfisher
-
-public protocol WorkerProfileViewModelable {
-
- // Input
- var viewWillAppear: PublishRelay { get }
-
- // Output
- var profileRenderObject: Driver? { get }
-}
-
-extension UIImageView {
-
- func setImage(url: URL) {
- let pngSerializer = FormatIndicatedCacheSerializer.png
- self
- .kf.setImage(
- with: url,
- options: [.cacheSerializer(pngSerializer)]
- )
- }
-}
public class WorkerProfileViewController: DisposableViewController {
private var viewModel: (any WorkerProfileViewModelable)?
// 네비게이션 바
- let navigationBar: NavigationBarType1 = {
- let bar = NavigationBarType1(navigationTitle: "")
+ lazy var navigationBar: IdleNavigationBar = {
+ let bar = IdleNavigationBar(titleText: "", innerViews: [profileEditButton])
return bar
}()
@@ -58,22 +36,14 @@ public class WorkerProfileViewController: DisposableViewController {
}()
// 프로필 이미지
- let profileImageContainer: UIView = {
+ let profileImageContainer: UIImageView = {
- let view = UIView()
+ let view = UIImageView()
view.backgroundColor = DSKitAsset.Colors.orange100.color
view.layer.cornerRadius = 48
view.clipsToBounds = true
-
- let imageView = DSKitAsset.Icons.workerProfilePlaceholder.image.toView()
- view.addSubview(imageView)
- imageView.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 26),
- imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25),
- imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -29),
- imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -27),
- ])
+ view.image = DSKitAsset.Icons.workerProfilePlaceholder.image
+ view.contentMode = .scaleAspectFit
return view
}()
@@ -87,6 +57,16 @@ public class WorkerProfileViewController: DisposableViewController {
return imageView
}()
+ // 즐겨찾기 버튼
+// public let starButton: IconWithColorStateButton = {
+// let button = IconWithColorStateButton(
+// representImage: DSKitAsset.Icons.subscribeStar.image,
+// normalColor: DSKitAsset.Colors.gray200.color,
+// accentColor: DSKitAsset.Colors.orange300.color
+// )
+// return button
+// }()
+
// 구인중 / 휴식중
let workingTag: TagLabel = {
let label = TagLabel(
@@ -146,6 +126,21 @@ public class WorkerProfileViewController: DisposableViewController {
return label
}()
+ // 통화하기 버튼
+ // 통화하기, 채팅하기
+ lazy var contactButtonContainer: HStack = .init(
+ [phoneCallButton],
+ spacing: 8,
+ distribution: .fillEqually
+ )
+ let phoneCallButton: IdleSecondaryButton = {
+ let button = IdleSecondaryButton(level: .medium)
+ button.label.textString = "통화하기"
+ return button
+ }()
+
+ // MARK: 상세정보
+
// 주소(Label + Content)
let addressTitleLabel: IdleLabel = {
let label = IdleLabel(typography: .Subtitle4)
@@ -200,25 +195,11 @@ public class WorkerProfileViewController: DisposableViewController {
}
private func setApearance() {
- view.backgroundColor = .white
- view.layoutMargins = .init(
- top: 0,
- left: 20,
- bottom: 0,
- right: 20
- )
+ view.backgroundColor = DSKitAsset.Colors.gray050.color
}
private func setAutoLayout() {
- // 상단 네비게이션바 세팅
- let navigationStack = HStack([
- navigationBar,
- profileEditButton,
- ])
- navigationStack.distribution = .equalSpacing
- navigationStack.backgroundColor = .clear
-
// 흑색 바탕
let grayBackgrounnd = UIView()
grayBackgrounnd.backgroundColor = DSKitAsset.Colors.gray050.color
@@ -284,6 +265,7 @@ public class WorkerProfileViewController: DisposableViewController {
}
}
+ // MARK: Divider
// 요양보호사 인적정보 / 요양보호사 구직정보 디바이더
let divider = UIView()
divider.backgroundColor = DSKitAsset.Colors.gray050.color
@@ -313,64 +295,141 @@ public class WorkerProfileViewController: DisposableViewController {
spacing: 28,
alignment: .leading)
- // view hierarchy
+ let contentView = UIView()
+ contentView.backgroundColor = DSKitAsset.Colors.gray0.color
+ contentView.layoutMargins = .init(
+ top: 0,
+ left: 20,
+ bottom: 54,
+ right: 20
+ )
+
[
grayBackgrounnd,
- navigationStack,
profileImageContainer,
+// starButton,
tagNameStack,
humanInfoStack,
+ contactButtonContainer,
divider,
employeeInfoTitleLabel,
employeeInfoStack
].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview($0)
+ contentView.addSubview($0)
}
- grayBackgrounnd.layer.zPosition = 0.0
- navigationStack.layer.zPosition = 1.0
NSLayoutConstraint.activate([
- grayBackgrounnd.topAnchor.constraint(equalTo: view.topAnchor),
- grayBackgrounnd.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- grayBackgrounnd.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- grayBackgrounnd.heightAnchor.constraint(equalToConstant: 196),
-
- navigationStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21),
- navigationStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12),
- navigationStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
+ grayBackgrounnd.topAnchor.constraint(equalTo: contentView.topAnchor),
+ grayBackgrounnd.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+ grayBackgrounnd.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+ grayBackgrounnd.heightAnchor.constraint(equalToConstant: 80),
profileImageContainer.widthAnchor.constraint(equalToConstant: 96),
profileImageContainer.heightAnchor.constraint(equalTo: profileImageContainer.widthAnchor),
- profileImageContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ profileImageContainer.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
profileImageContainer.centerYAnchor.constraint(equalTo: grayBackgrounnd.bottomAnchor),
+// starButton.widthAnchor.constraint(equalToConstant: 24),
+// starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor),
+// starButton.topAnchor.constraint(equalTo: grayBackgrounnd.bottomAnchor, constant: 20),
+// starButton.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20),
+
tagNameStack.topAnchor.constraint(equalTo: profileImageContainer.bottomAnchor, constant: 16),
- tagNameStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ tagNameStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
humanInfoStack.topAnchor.constraint(equalTo: tagNameStack.bottomAnchor, constant: 16),
- humanInfoStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ humanInfoStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
- divider.topAnchor.constraint(equalTo: humanInfoStack.bottomAnchor, constant: 24),
- divider.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- divider.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ contactButtonContainer.topAnchor.constraint(equalTo: humanInfoStack.bottomAnchor, constant: 24),
+ contactButtonContainer.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 36),
+ contactButtonContainer.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -36),
+
+ divider.topAnchor.constraint(equalTo: contactButtonContainer.bottomAnchor, constant: 24),
+ divider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+ divider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
divider.heightAnchor.constraint(equalToConstant: 8),
employeeInfoTitleLabel.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 24),
- employeeInfoTitleLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
+ employeeInfoTitleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
employeeInfoStack.topAnchor.constraint(equalTo: employeeInfoTitleLabel.bottomAnchor, constant: 20),
- employeeInfoStack.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- employeeInfoStack.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ employeeInfoStack.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ employeeInfoStack.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+ employeeInfoStack.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
+ ])
+
+ let scrollView = UIScrollView()
+ scrollView.delaysContentTouches = false
+ let contentGuide = scrollView.contentLayoutGuide
+ let frameGuide = scrollView.frameLayoutGuide
+
+ scrollView.addSubview(contentView)
+ contentView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ contentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
+ contentView.leftAnchor.constraint(equalTo: contentGuide.leftAnchor),
+ contentView.rightAnchor.constraint(equalTo: contentGuide.rightAnchor),
+ contentView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor),
+
+ contentView.widthAnchor.constraint(equalTo: frameGuide.widthAnchor),
+ ])
+
+
+ [
+ navigationBar,
+ scrollView,
+ ].forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview($0)
+ }
+
+ NSLayoutConstraint.activate([
+
+ navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
+ navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+
+ scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
+ scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
+ scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
+ scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
- public func bind(_ viewModel: any WorkerProfileViewModelable) {
+ private func setObservable() {
+
+ navigationBar
+ .backButton.rx.tap
+ .observe(on: MainScheduler.instance)
+ .subscribe { [weak self] _ in
+ self?.navigationController?.popViewController(animated: true)
+ }
+ .disposed(by: disposeBag)
+ }
+
+ /// 다른 프로필 확인용 바인딩 입니다.
+ public func bind(_ viewModel: OtherWorkerProfileViewModelable) {
+ bind(viewModel as WorkerProfileViewModelable)
+
+ phoneCallButton
+ .rx.tap
+ .bind(to: viewModel.phoneCallButtonClicked)
+ .disposed(by: disposeBag)
+ }
+
+ /// 내프로필 접속용 바인딩 입니다.
+ public func bind(_ viewModel: WorkerProfileEditViewModelable) {
+ bind(viewModel as WorkerProfileViewModelable)
+ }
+
+ private func bind(_ viewModel: any WorkerProfileViewModelable) {
self.viewModel = viewModel
+ // Input
profileEditButton
.eventPublisher
.observe(on: MainScheduler.instance)
@@ -382,13 +441,18 @@ public class WorkerProfileViewController: DisposableViewController {
})
.disposed(by: disposeBag)
- // Input
+ navigationBar
+ .backButton.rx.tap
+ .bind(to: viewModel.exitButtonClicked)
+ .disposed(by: disposeBag)
+
self.rx
.viewWillAppear
.filter { $0 }
.map { _ in () }
.bind(to: viewModel.viewWillAppear)
.disposed(by: disposeBag)
+
// Output
viewModel
@@ -398,16 +462,19 @@ public class WorkerProfileViewController: DisposableViewController {
guard let self else { return }
// UI 업데이트
- navigationBar.navigationTitle = ro.navigationTitle
+ navigationBar.titleLabel.textString = ro.navigationTitle
profileEditButton.isHidden = !ro.showEditButton
+ contactButtonContainer.isHidden = !ro.showContactButton
+// starButton.isHidden = !ro.showStarButton
workingTag.textString = ro.stateText
nameLabel.textString = ro.nameText
ageLabel.textString = ro.ageText
genderLabel.textString = ro.genderText
expLabel.textString = ro.expText
+
addressLabel.textString = ro.address
- introductionLabel.textString = ro.oneLineIntroduce
- abilityLabel.textString = ro.specialty
+ introductionLabel.textString = ro.oneLineIntroduce.emptyDefault("-")
+ abilityLabel.textString = ro.specialty.emptyDefault("-")
if let imageUrl = ro.imageUrl {
workerProfileImage.setImage(url: imageUrl)
@@ -421,6 +488,8 @@ public class WorkerProfileViewController: DisposableViewController {
}
}
+
+
@available(iOS 17.0, *)
#Preview("Preview", traits: .defaultLayout) {
diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RenderObject/WorkerProfileRenderObject.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RenderObject/WorkerProfileRenderObject.swift
index 66b0531c..ab991549 100644
--- a/project/Projects/Presentation/Feature/Worker/Sources/View/RenderObject/WorkerProfileRenderObject.swift
+++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RenderObject/WorkerProfileRenderObject.swift
@@ -13,6 +13,8 @@ public struct WorkerProfileRenderObject {
let navigationTitle: String
let showEditButton: Bool
+ let showContactButton: Bool
+// let showStarButton: Bool
let isJobFinding: Bool
let stateText: String
let nameText: String
@@ -30,6 +32,8 @@ public struct WorkerProfileRenderObject {
.init(
navigationTitle: isMyProfile ? "내 프로필" : "요양보호사 프로필",
showEditButton: isMyProfile,
+ showContactButton: !isMyProfile,
+// showStarButton: !isMyProfile,
isJobFinding: vo.isLookingForJob,
stateText: vo.isLookingForJob ? "구인중" : "휴식중",
nameText: vo.nameText,
diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerMyProfileViewModel.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift
similarity index 80%
rename from project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerMyProfileViewModel.swift
rename to project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift
index 474bf458..5cb5a7d3 100644
--- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerMyProfileViewModel.swift
+++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift
@@ -13,24 +13,42 @@ import DSKit
import Entity
import UseCaseInterface
+public protocol WorkerProfileEditViewModelable: WorkerProfileViewModelable {
+
+ var requestUpload: PublishRelay { get }
+ var editingImage: PublishRelay { get }
+ var editingIsJobFinding: PublishRelay { get }
+ var editingExpYear: PublishRelay { get }
+ var editingAddress: PublishRelay { get }
+ var editingIntroduce: PublishRelay { get }
+ var editingSpecialty: PublishRelay { get }
+
+ var uploadSuccess: Driver? { get }
+ var alert: Driver? { get }
+}
+
public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable {
+ public weak var coordinator: WorkerProfileCoordinator?
+
let workerProfileUseCase: WorkerProfileUseCase
// Input(Editing)
- var requestUpload: PublishRelay = .init()
- var editingImage: PublishRelay = .init()
- var editingIsJobFinding: PublishRelay = .init()
- var editingExpYear: PublishRelay = .init()
- var editingAddress: PublishRelay = .init()
- var editingIntroduce: PublishRelay = .init()
- var editingSpecialty: PublishRelay = .init()
+ public var requestUpload: PublishRelay = .init()
+ public var editingImage: PublishRelay = .init()
+ public var editingIsJobFinding: PublishRelay = .init()
+ public var editingExpYear: PublishRelay = .init()
+ public var editingAddress: PublishRelay = .init()
+ public var editingIntroduce: PublishRelay = .init()
+ public var editingSpecialty: PublishRelay = .init()
// Input(Rendering)
public var viewWillAppear: PublishRelay = .init()
+ public var exitButtonClicked: RxRelay.PublishRelay = .init()
+
// Output
- var uploadSuccess: Driver?
+ public var uploadSuccess: Driver?
public var alert: Driver?
public var profileRenderObject: Driver?
@@ -43,8 +61,12 @@ public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable {
let disposbag: DisposeBag = .init()
- public init(workerProfileUseCase: WorkerProfileUseCase) {
+ public init(
+ coordinator: WorkerProfileCoordinator?,
+ workerProfileUseCase: WorkerProfileUseCase
+ ) {
+ self.coordinator = coordinator
self.workerProfileUseCase = workerProfileUseCase
// Input(Rendering)
@@ -80,6 +102,12 @@ public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable {
.bind(to: rederingState)
.disposed(by: disposbag)
+ exitButtonClicked
+ .subscribe(onNext: { [weak self] in
+ self?.coordinator?.coordinatorDidFinish()
+ })
+ .disposed(by: disposbag)
+
// Edit Input
let imageValidationResult = editingImage
diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerProfileViewModel.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift
similarity index 69%
rename from project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerProfileViewModel.swift
rename to project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift
index e523327c..0a07f9be 100644
--- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerProfileViewModel.swift
+++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift
@@ -13,7 +13,15 @@ import DSKit
import Entity
import UseCaseInterface
-public class WorkerProfileViewModel: WorkerProfileViewModelable {
+/// 자신의 프로필을 확인하는 경우가 아닌 센터측에서 요양보호사를 보는 경우
+public protocol OtherWorkerProfileViewModelable: WorkerProfileViewModelable {
+
+ var phoneCallButtonClicked: PublishRelay { get }
+}
+
+public class WorkerProfileViewModel: OtherWorkerProfileViewModelable {
+
+ public weak var coordinator: WorkerProfileCoordinator?
let workerProfileUseCase: WorkerProfileUseCase
@@ -22,6 +30,8 @@ public class WorkerProfileViewModel: WorkerProfileViewModelable {
// Input(Rendering)
public var viewWillAppear: PublishRelay = .init()
+ public var exitButtonClicked: PublishRelay = .init()
+ public var phoneCallButtonClicked: PublishRelay = .init()
// Output
var uploadSuccess: Driver?
@@ -37,8 +47,13 @@ public class WorkerProfileViewModel: WorkerProfileViewModelable {
let disposbag: DisposeBag = .init()
- public init(workerProfileUseCase: WorkerProfileUseCase, workerId: String) {
+ public init(
+ coordinator: WorkerProfileCoordinator?,
+ workerProfileUseCase: WorkerProfileUseCase,
+ workerId: String
+ ) {
+ self.coordinator = coordinator
self.workerProfileUseCase = workerProfileUseCase
self.workerId = workerId
@@ -66,6 +81,20 @@ public class WorkerProfileViewModel: WorkerProfileViewModelable {
return vo
}
+ exitButtonClicked
+ .subscribe(onNext: { [weak self] in
+ self?.coordinator?.coordinatorDidFinish()
+ })
+ .disposed(by: disposbag)
+
+ phoneCallButtonClicked
+ .subscribe(onNext: { _ in
+
+ // 안심번호 전화연결
+
+ })
+ .disposed(by: disposbag)
+
fetchedProfileVOSuccess
.asObservable()
.map({ vo in
diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModelable.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModelable.swift
new file mode 100644
index 00000000..c2b7a687
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModelable.swift
@@ -0,0 +1,24 @@
+//
+// File.swift
+// WorkerFeature
+//
+// Created by choijunios on 8/14/24.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+import Entity
+
+
+public protocol WorkerProfileViewModelable {
+
+ var coordinator: WorkerProfileCoordinator? { get }
+
+ // Input
+ var viewWillAppear: PublishRelay { get }
+ var exitButtonClicked: PublishRelay { get }
+
+ // Output
+ var profileRenderObject: Driver? { get }
+}
diff --git a/project/Projects/Presentation/PresentationCore/Sources/Extensions/String+Extension.swift b/project/Projects/Presentation/PresentationCore/Sources/Extensions/String+Extension.swift
new file mode 100644
index 00000000..51dab598
--- /dev/null
+++ b/project/Projects/Presentation/PresentationCore/Sources/Extensions/String+Extension.swift
@@ -0,0 +1,15 @@
+//
+// String+Extension.swift
+// PresentationCore
+//
+// Created by choijunios on 8/14/24.
+//
+
+import Foundation
+
+public extension String {
+
+ func emptyDefault(_ str: String) -> String {
+ self.isEmpty ? str : self
+ }
+}
diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift
index 6760b66b..7fdb10a3 100644
--- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift
+++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift
@@ -8,6 +8,5 @@
import Foundation
public protocol CenterMainCoordinatable: ParentCoordinator {
- func centerProfileRegister()
- func registerRecruitmentPost()
+
}
diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift
new file mode 100644
index 00000000..ea8a3374
--- /dev/null
+++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift
@@ -0,0 +1,15 @@
+//
+// CenterPostBoardCoordinatable.swift
+// PresentationCore
+//
+// Created by choijunios on 8/13/24.
+//
+
+import Foundation
+
+public protocol CenterPostBoardTabCoordinatable: ParentCoordinator {
+
+ func showApplicantScreen()
+ func postDetailScreen()
+ func showPostEditScreen()
+}
diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift
new file mode 100644
index 00000000..d4193255
--- /dev/null
+++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift
@@ -0,0 +1,14 @@
+//
+// CheckApplicantCoordinatable.swift
+// PresentationCore
+//
+// Created by choijunios on 8/13/24.
+//
+
+import Foundation
+
+public protocol CheckApplicantCoordinatable: ParentCoordinator {
+
+ func taskFinished()
+ func showWorkerProfileScreen(profileId: String)
+}
diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift
new file mode 100644
index 00000000..5ca23ec9
--- /dev/null
+++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift
@@ -0,0 +1,13 @@
+//
+// RecruitmentManagementCoordinatable.swift
+// PresentationCore
+//
+// Created by choijunios on 8/13/24.
+//
+
+import Entity
+
+public protocol RecruitmentManagementCoordinatable: ParentCoordinator {
+
+ func showCheckingApplicantScreen(_ centerEmployCardVO: CenterEmployCardVO)
+}