Skip to content

Commit 8e3199a

Browse files
authored
[Merge] #187 - 취득예정, 취득완료(수정 및 삭제 포함) 자격증 컴포넌트
2 parents f6d0852 + cff51ec commit 8e3199a

7 files changed

Lines changed: 281 additions & 0 deletions

File tree

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
//
2+
// MyCertificationItem.swift
3+
// CERTI-iOS
4+
//
5+
// Created by OneTen on 11/23/25.
6+
//
7+
8+
import SwiftUI
9+
10+
struct MyCertificationItem: View {
11+
12+
enum CertificationType {
13+
case completed(date: String, score: String?)
14+
case expected(location: String, time: String)
15+
16+
var text: String {
17+
switch self {
18+
case .completed: return "취득 완료"
19+
case .expected: return "취득 예정"
20+
}
21+
}
22+
23+
var color: Color {
24+
switch self {
25+
case .completed: return .mainblue
26+
case .expected: return .purpleblue
27+
}
28+
}
29+
}
30+
31+
enum EditType {
32+
case viewOnly // 수정, 삭제 ㄴㄴ
33+
case editable(onEdit: () -> Void, onDelete: () -> Void) // 수정, 삭제 ㅇㅇ
34+
}
35+
36+
37+
// MARK: - Properties
38+
39+
let type: CertificationType
40+
let title: String
41+
let category: String
42+
let description: String
43+
let actionConfig: EditType
44+
45+
46+
// MARK: - Main Body
47+
48+
var body: some View {
49+
VStack(alignment: .leading, spacing: 0) {
50+
51+
topBadge
52+
.padding(.bottom, 8)
53+
54+
titleSection
55+
.padding(.bottom, 4)
56+
57+
Text(description)
58+
.applyCertiFont(.caption_regular_12)
59+
.foregroundStyle(.grayscale500)
60+
.frame(height: 18)
61+
.padding(.bottom, 12)
62+
63+
infoSection
64+
65+
}
66+
.padding(16)
67+
.background(.white)
68+
.clipShape(RoundedRectangle(cornerRadius: 12))
69+
.overlay(
70+
RoundedRectangle(cornerRadius: 12)
71+
.stroke(.lightpurple, lineWidth: 1)
72+
)
73+
}
74+
}
75+
76+
77+
// MARK: - Subviews
78+
79+
extension MyCertificationItem {
80+
@ViewBuilder
81+
private var topBadge: some View {
82+
HStack(alignment: .center, spacing: 0) {
83+
HStack(alignment: .center, spacing: 4) {
84+
Image(systemName: "checkmark.circle.fill")
85+
.resizable()
86+
.scaledToFit()
87+
.frame(width: 10, height: 10)
88+
89+
Text(type.text)
90+
.applyCertiFont(.caption_semibold_10)
91+
}
92+
.foregroundStyle(.white)
93+
.padding(6)
94+
.background(type.color)
95+
.clipShape(Capsule())
96+
97+
Spacer()
98+
99+
switch actionConfig {
100+
case .viewOnly:
101+
EmptyView()
102+
case .editable(let onEdit, let onDelete):
103+
HStack(alignment: .center, spacing: 12) {
104+
actionButton(title: "수정", action: onEdit)
105+
actionButton(title: "삭제", action: onDelete)
106+
}
107+
}
108+
}
109+
}
110+
111+
@ViewBuilder
112+
private var titleSection: some View {
113+
HStack(alignment: .center, spacing: 8) {
114+
Text(title)
115+
.applyCertiFont(.sub_semibold_20)
116+
117+
Text(category)
118+
.applyCertiFont(.caption_regular_12)
119+
}
120+
.foregroundStyle(.black)
121+
.frame(height: 26)
122+
}
123+
124+
@ViewBuilder
125+
private var infoSection: some View {
126+
HStack(alignment: .center, spacing: 8) {
127+
switch type {
128+
case .expected(let location, let time):
129+
// [취득 예정] 장소 + 시간
130+
infoItem(icon: .iconPlacemark16, text: location)
131+
infoItem(icon: .iconTime16, text: time)
132+
133+
case .completed(let date, let score):
134+
// [취득 완료] 날짜 + 점수
135+
infoItem(icon: .iconDate16, text: date)
136+
infoItem(icon: .iconLevel16, text: score ?? "취득 점수 -")
137+
}
138+
}
139+
}
140+
141+
@ViewBuilder
142+
private func infoItem(icon: UIImage, text: String) -> some View {
143+
HStack(spacing: 4) {
144+
Image(uiImage: icon)
145+
.foregroundStyle(.grayscale300)
146+
147+
Text(text)
148+
.applyCertiFont(.caption_regular_14)
149+
.foregroundStyle(text == "취득 점수 -" ? .grayscale200 : .black)
150+
}
151+
}
152+
153+
@ViewBuilder
154+
private func actionButton(title: String, action: @escaping () -> Void) -> some View {
155+
Button {
156+
action()
157+
} label: {
158+
Text(title)
159+
.applyCertiFont(.caption_regular_12)
160+
.foregroundStyle(.grayscale600)
161+
.padding(.horizontal, 12)
162+
.padding(.vertical, 4)
163+
.background(.white)
164+
.overlay(
165+
Capsule()
166+
.stroke(.grayscale300, lineWidth: 1)
167+
)
168+
}
169+
}
170+
}
171+
172+
#Preview {
173+
VStack(spacing: 20) {
174+
175+
// case 1: 취득 예정, 버튼 없음
176+
MyCertificationItem(
177+
type: .expected(location: "고양시", time: "09:00"),
178+
title: "정보처리기사",
179+
category: "국가기술자격",
180+
description: "소프트웨어 개발 관련 자격증...",
181+
actionConfig: .viewOnly
182+
)
183+
184+
// case 2: 취득 예정, 버튼 있음
185+
MyCertificationItem(
186+
type: .expected(location: "서울시", time: "14:00"),
187+
title: "정보보안기사",
188+
category: "국가기술자격",
189+
description: "보안 관련 자격증...",
190+
actionConfig: .editable(onEdit: {
191+
print("수정")
192+
}, onDelete: {
193+
print("삭제")
194+
})
195+
)
196+
197+
// case 3: 취득 완료, 버튼 없음, 점수 있음
198+
MyCertificationItem(
199+
type: .completed(date: "2023. 11. 23", score: "IM3"),
200+
title: "OPIC",
201+
category: "어학",
202+
description: "영어 말하기 시험...",
203+
actionConfig: .viewOnly
204+
)
205+
206+
// case 4: 취득 완료, 버튼 있음, 점수 없음
207+
MyCertificationItem(
208+
type: .completed(date: "2023. 11. 23", score: nil),
209+
title: "OPIC",
210+
category: "어학",
211+
description: "영어 말하기 시험...",
212+
actionConfig: .editable(onEdit: {
213+
print("수정")
214+
}, onDelete: {
215+
print("삭제")
216+
})
217+
)
218+
}
219+
.padding()
220+
.background(Color.gray.opacity(0.1))
221+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "icon_16_level.svg",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"preserves-vector-representation" : true
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "icon_placemark_16.svg",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"preserves-vector-representation" : true
14+
}
15+
}
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "icon_time_16.svg",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"preserves-vector-representation" : true
14+
}
15+
}
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)