Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
484984b
[FEAT] 입출고 히스토리 구현
Yoo-Hyuna Nov 2, 2025
ab2d4fa
[FIX] 주문 상태 enum 변경에 따른 매핑 수정
Yoo-Hyuna Nov 2, 2025
f8330b0
[FEAT] 홈화면 지출현황 중간 코드 저장
Yoo-Hyuna Nov 2, 2025
79ca35e
[FEAT] 홈화면 파이차트 중간 저장
Yoo-Hyuna Nov 3, 2025
40ef1db
[FEAT] 홈화면 대시보드 기능 구현
Yoo-Hyuna Nov 3, 2025
72deb00
[FEAT] 홈화면 대시보드 기능 구현
Yoo-Hyuna Nov 3, 2025
92a5c27
[FEAT] 에치금 히스토리 임시 저장
Yoo-Hyuna Nov 3, 2025
f2246cd
[FEAT] 부품 상세 조회 api 연결
Yoo-Hyuna Nov 3, 2025
c64204b
[FEAT] 부품 사용처리 기능 구현 임시 저장
Yoo-Hyuna Nov 3, 2025
5edc05f
[FEAT] 부품 사용처리 기능 구현 임시 저장
Yoo-Hyuna Nov 3, 2025
e4ee734
[FEAT] 부품 사용처리 기능 구현
Yoo-Hyuna Nov 3, 2025
2d5a3da
[FEAT] 부품 사용처리 구현
Yoo-Hyuna Nov 4, 2025
9e1805d
[REFAC] 입출고 히스토리 시간대 수정
Yoo-Hyuna Nov 4, 2025
1b8843c
[REFAC] 버튼 리팩터링 및 스켈레톤 UI 적용
Yoo-Hyuna Nov 4, 2025
43d3d93
[FEAT] 마이페이지 구현
Yoo-Hyuna Nov 5, 2025
58b8fc0
[FEAT] 회원가입 시 카카오 우편번호 조회 기능 추가
Yoo-Hyuna Nov 5, 2025
c8419f9
[FEAT] 예치금 히스토리 기능 임시저장
Yoo-Hyuna Nov 6, 2025
5bcbaf8
[FEAT] 예치금 히스토리 기능 구현
Yoo-Hyuna Nov 6, 2025
9db5ff8
[FEAT] 예치금 히스토리 기능 구현
Yoo-Hyuna Nov 6, 2025
d813871
[REFAC] stausText 및 마이페이지 수정
Yoo-Hyuna Nov 7, 2025
5ceb482
[REFAC] 코드리뷰 적용
Yoo-Hyuna Nov 7, 2025
26fe712
[REFAC] 코드리뷰 적용
Yoo-Hyuna Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified StockMate/.DS_Store
Binary file not shown.
71 changes: 17 additions & 54 deletions StockMate/StockMate/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,67 +7,30 @@

import SwiftUI

//struct ContentView: View {
// @State private var showingScanner = false
// @State private var scannedCode: String? = nil
//
// var body: some View {
// NavigationView {
// VStack(spacing: 20) {
// if let code = scannedCode {
// Text("스캔 결과:")
// .font(.headline)
// Text(code)
// .font(.body)
// .multilineTextAlignment(.center)
// .padding()
// .background(Color(.systemGray6))
// .cornerRadius(8)
// } else {
// Text("아직 스캔된 코드가 없습니다.")
// .foregroundColor(.secondary)
// }
//
// Button("QR 스캔 시작") {
// // 카메라 권한 체크는 시스템이 자동으로 권한 알림을 띄우므로
// // 필요하면 권한 상태 확인 로직 추가 가능
// showingScanner = true
// }
// .buttonStyle(.borderedProminent)
// .padding(.top)
//
// Spacer()
// }
// .padding()
// .navigationTitle("QR 스캐너 예제")
// .sheet(isPresented: $showingScanner) {
// QRScannerView(isPresented: $showingScanner, scannedCode: $scannedCode)
// .edgesIgnoringSafeArea(.all)
// }
// }
// }
//}
struct ContentView: View {
@State private var address: String = "주소를 선택하세요"
@State private var showWebView = false

var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("임시 화면")
}
.padding()
HStack(spacing: 20) {
Image(systemName: "gearshape")
.font(.system(size: 40))
.foregroundColor(.blue)
VStack(spacing: 20) {
Text(address)
.font(.title3)
.multilineTextAlignment(.center)
.padding()

Image(systemName: "lightbulb")
.font(.system(size: 40))
.foregroundColor(.cyan)
Button("주소 검색") {
showWebView.toggle()
}
.font(.headline)
.buttonStyle(.borderedProminent)
}
.sheet(isPresented: $showWebView) {
KakaoZipCodeView(address: $address)
}
}
}


#Preview {
ContentView()
}
80 changes: 80 additions & 0 deletions StockMate/StockMate/app/core/KakaoPostcode/KakaoZipCodeVC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// KakaoZipCodeVC.swift
// StockMate
//
// Created by Admin on 11/5/25.
//


import UIKit
import WebKit

class KakaoZipCodeVC: UIViewController {

// MARK: - Properties
var webView: WKWebView?
let indicator = UIActivityIndicatorView(style: .medium)
var onAddressSelected: ((String) -> Void)? // ✅ SwiftUI로 결과 전달용 콜백

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupWebView()
setupLayout()
}

private func setupWebView() {
let contentController = WKUserContentController()
contentController.add(self, name: "callBackHandler")

let config = WKWebViewConfiguration()
config.userContentController = contentController

webView = WKWebView(frame: .zero, configuration: config)
webView?.navigationDelegate = self

guard let webView = webView,
let url = URL(string: "https://yoo-hyuna.github.io/Kakao-Postcode/") else { return }

webView.load(URLRequest(url: url))
}
Comment on lines +26 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

메모리 누수를 수정해야 합니다.

Line 28에서 contentController.add(self, name: "callBackHandler")self에 대한 강한 참조를 생성하여 메모리 누수를 발생시킵니다. WKUserContentController가 메시지 핸들러를 강하게 유지하므로 뷰 컨트롤러가 해제되지 않습니다.

해결 방법 1: deinit에서 메시지 핸들러 제거

+    deinit {
+        webView?.configuration.userContentController.removeScriptMessageHandler(forName: "callBackHandler")
+    }

해결 방법 2: WeakScriptMessageDelegate 패턴 사용

private class WeakScriptMessageDelegate: NSObject, WKScriptMessageHandler {
    weak var delegate: WKScriptMessageHandler?
    
    init(delegate: WKScriptMessageHandler) {
        self.delegate = delegate
    }
    
    func userContentController(_ userContentController: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        delegate?.userContentController(userContentController, didReceive: message)
    }
}

그리고 setupWebView에서:

-contentController.add(self, name: "callBackHandler")
+let weakDelegate = WeakScriptMessageDelegate(delegate: self)
+contentController.add(weakDelegate, name: "callBackHandler")
🤖 Prompt for AI Agents
In StockMate/StockMate/app/core/KakaoPostcode/KakaoZipCodeVC.swift around lines
26 to 40, adding self as a WKScriptMessageHandler creates a strong reference
cycle that prevents the view controller from being deallocated; fix by using a
weak wrapper or removing the handler in deinit: implement a private
WeakScriptMessageDelegate class conforming to WKScriptMessageHandler that holds
a weak reference to the real delegate and forwards
userContentController(_:didReceive:), then in setupWebView add
WeakScriptMessageDelegate(delegate: self) instead of self; also implement deinit
to call
webView.configuration.userContentController.removeScriptMessageHandler(forName:)
(or remove the handler from your stored contentController) to ensure cleanup if
using the direct handler approach.


private func setupLayout() {
guard let webView = webView else { return }
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false

webView.addSubview(indicator)
indicator.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

indicator.centerXAnchor.constraint(equalTo: webView.centerXAnchor),
indicator.centerYAnchor.constraint(equalTo: webView.centerYAnchor)
])
}
}

extension KakaoZipCodeVC: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
guard let data = message.body as? [String: Any] else { return }
let address = data["roadAddress"] as? String ?? ""
onAddressSelected?(address) // ✅ SwiftUI로 전달
dismiss(animated: true)
}
}

extension KakaoZipCodeVC: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
indicator.startAnimating()
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
indicator.stopAnimating()
}
}
24 changes: 24 additions & 0 deletions StockMate/StockMate/app/core/KakaoPostcode/KakaoZipCodeView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// KakaoZipCodeView.swift
// StockMate
//
// Created by Admin on 11/5/25.
//


import SwiftUI
import WebKit

struct KakaoZipCodeView: UIViewControllerRepresentable {
@Binding var address: String

func makeUIViewController(context: Context) -> KakaoZipCodeVC {
let vc = KakaoZipCodeVC()
vc.onAddressSelected = { selectedAddress in
address = selectedAddress
}
return vc
}

func updateUIViewController(_ uiViewController: KakaoZipCodeVC, context: Context) {}
}
53 changes: 53 additions & 0 deletions StockMate/StockMate/app/core/KakaoPostcode/ViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// ViewController.swift
// StockMate
//
// Created by Admin on 11/5/25.
//


import UIKit

class ViewController: UIViewController {

// MARK: - UI Components
let button = UIButton(type: .system)
let label = UILabel()

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
configureUI()
}

private func configureUI() {
[label, button].forEach {
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}

label.text = "주소를 선택하세요"
label.font = UIFont.systemFont(ofSize: 18)
label.textAlignment = .center

button.setTitle("주소 검색", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
button.addTarget(self, action: #selector(handleButton(_:)), for: .touchUpInside)

NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50),
label.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),

button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 30)
])
}

@objc
private func handleButton(_ sender: UIButton) {
let vc = KakaoZipCodeVC()
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
Comment on lines +47 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

선택된 주소를 처리하는 콜백이 누락되었습니다.

KakaoZipCodeVC를 present하지만 onAddressSelected 콜백을 설정하지 않아 선택된 주소가 반영되지 않습니다. 이 컨트롤러가 데모/테스트용이라면 주석으로 명시하거나, 프로덕션 코드라면 콜백을 구현해야 합니다.

다음과 같이 콜백을 추가하세요:

 @objc
 private func handleButton(_ sender: UIButton) {
     let vc = KakaoZipCodeVC()
+    vc.onAddressSelected = { [weak self] selectedAddress in
+        self?.label.text = selectedAddress
+    }
     vc.modalPresentationStyle = .fullScreen
     present(vc, animated: true)
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In StockMate/StockMate/app/core/KakaoPostcode/ViewController.swift around lines
47 to 52, you present KakaoZipCodeVC but never set its onAddressSelected
callback so the selected address is never propagated; set vc.onAddressSelected =
{ [weak self] address in ... } to update the view controller’s address state/UI
(and dismiss or pop the presented VC) or, if this presentation is only for demo,
add a clear TODO/comment indicating it is intentionally unhandled. Ensure you
capture self weakly, perform UI updates on the main thread, and dismiss the
KakaoZipCodeVC inside the callback after saving the address.

}
140 changes: 140 additions & 0 deletions StockMate/StockMate/app/core/components/AlertModal.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// AlertModal.swift
// StockMate
//
// Created by Admin on 11/4/25.
//


import SwiftUI

struct AlertModal: View {
var icon: Image? = nil // ✅ 아이콘 없을 수도 있음
var title: String
var message: String? = nil

var primaryButtonTitle: String
var primaryAction: () -> Void

var secondaryButtonTitle: String? = nil
var secondaryAction: (() -> Void)? = nil

var buttonLayout: ButtonLayout = .vertical // ✅ horizontal / vertical

enum ButtonLayout {
case vertical
case horizontal
}

var body: some View {
VStack(spacing: 15) {
if let icon = icon {
icon
.resizable()
.scaledToFit()
.frame(width: 120, height: 120)
.padding(.top, 6)
}

Text(title)
.font(.system(size: 18, weight: .bold))
.multilineTextAlignment(.center)
.padding(.top, 10)

if let message = message {
Text(message)
.font(.system(size: 14, weight: .regular))
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.bottom, 6)
}

if buttonLayout == .vertical {
VStack(spacing: 10) {
if let secondary = secondaryButtonTitle, let secondaryAction = secondaryAction {
Button(secondary, action: secondaryAction)
.buttonStyle(CustomButtonStyle(type: .outlined(.Primary)))
}
Button(primaryButtonTitle, action: primaryAction)
.buttonStyle(CustomButtonStyle(type: .filled(Color.Primary)))
}
} else {
HStack(spacing: 10) {
if let secondary = secondaryButtonTitle, let secondaryAction = secondaryAction {
Button(secondary, action: secondaryAction)
.buttonStyle(CustomButtonStyle(type: .outlined(.Primary)))
}
Button(primaryButtonTitle, action: primaryAction)
.buttonStyle(CustomButtonStyle(type: .filled(Color.Primary)))
}
}
}
.padding(20)
.frame(maxWidth: 300)
.background(Color.white)
.cornerRadius(32)
.shadow(radius: 8)
}
}

import SwiftUI

#Preview {
ScrollView{
VStack(spacing: 40) {
// ✅ 1. 체크 아이콘 + 버튼 1개
AlertModal(
icon: Image("SuccessIllust"),
title: "등록 완료!",
message: "입고 부품 등록이 완료되었습니다.",
primaryButtonTitle: "확인",
primaryAction: {}
)
AlertModal(
icon: Image("SuccessIllust"),
title: "출고 완료!",
message: "사용 처리가 완료되었습니다.",
primaryButtonTitle: "확인",
primaryAction: {}
)



// ✅ 2. 아이콘 없이 버튼 2개 (가로)
AlertModal(
title: "주문 취소",
message: "주문을 취소하시겠습니까?",
primaryButtonTitle: "예",
primaryAction: {},
secondaryButtonTitle: "아니오",
secondaryAction: {},
buttonLayout: .horizontal
)
AlertModal(
title: "로그아웃",
message: "로그아웃 하시겠습니까?",
primaryButtonTitle: "로그아웃",
primaryAction: {},
secondaryButtonTitle: "취소",
secondaryAction: {},
buttonLayout: .horizontal
)


// ✅ 3. 주문완료
AlertModal(
icon: Image("SuccessIllust"),
title: "주문완료!",
message: "해당 부품 주문이 완료되었습니다.",
primaryButtonTitle: "주문상세",
primaryAction: {},
secondaryButtonTitle: "홈으로",
secondaryAction: {},
buttonLayout: .vertical
)
}
.padding()
.background(Color.gray.opacity(0.1))
}

}
Loading