Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions StockMate/StockMate.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,22 @@
84BB0A132E91FE0E00A08CD6 /* StockMate.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StockMate.app; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
84BB0D3C2EB49C0900A08CD6 /* Exceptions for "StockMate" folder in "StockMate" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 84BB0A122E91FE0E00A08CD6 /* StockMate */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
84BB0A152E91FE0E00A08CD6 /* StockMate */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
84BB0D3C2EB49C0900A08CD6 /* Exceptions for "StockMate" folder in "StockMate" target */,
);
path = StockMate;
sourceTree = "<group>";
};
Expand Down Expand Up @@ -263,6 +276,8 @@
DEVELOPMENT_TEAM = 35TSG7VB2B;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = StockMate/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "QR 코드를 스캔하기 위해 카메라 접근이 필요합니다.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down Expand Up @@ -291,6 +306,8 @@
DEVELOPMENT_TEAM = 35TSG7VB2B;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = StockMate/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "QR 코드를 스캔하기 위해 카메라 접근이 필요합니다.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down
40 changes: 40 additions & 0 deletions StockMate/StockMate/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,46 @@

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 {
var body: some View {
VStack {
Expand Down
5 changes: 5 additions & 0 deletions StockMate/StockMate/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
1 change: 1 addition & 0 deletions StockMate/StockMate/app/feature/auth/ui/LoginView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ struct LoginView: View {
emailError = isValidEmail(authViewModel.email) ? nil : "이메일 형식을 확인해주세요"
pwError = authViewModel.password.count >= 8 ? nil : "8자 이상 비밀번호를 입력해주세요"
return emailError == nil && pwError == nil
return true
}

}
124 changes: 86 additions & 38 deletions StockMate/StockMate/app/feature/inventory/ui/IncomingScanView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,100 @@
import SwiftUI

struct IncomingScanView: View {
@Environment(\.dismiss) private var dismiss
@State private var scannedCode: String? = nil
@State private var showAlert = false
@State private var alertMessage = ""

@StateObject private var orderViewModel = OrderViewModel() // ✅ 뷰모델 추가

var body: some View {
VStack(spacing: 30) {
// 상단 타이틀
Text("입고 부품의 QR을 스캔해주세요")
.font(.headline)
.padding(.top, 30)

// 스캔 영역
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.2))
.frame(width: 250, height: 250)

RoundedRectangle(cornerRadius: 8)
.stroke(Color.blue, lineWidth: 3)
.frame(width: 220, height: 220)
ZStack {
// ✅ 1. 카메라 화면 (QR 스캐너)
QRScannerView(scannedCode: $scannedCode)
.ignoresSafeArea()

// ✅ 2. 스캔 영역 가이드 박스
VStack {
Text("입고 부품의 QR을 스캔해주세요")
.font(.headline)
.padding(.top, 60)
.foregroundColor(.white)
.shadow(radius: 2)

Spacer()

ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(Color.clear)
.frame(width: 250, height: 250)

RoundedRectangle(cornerRadius: 8)
.stroke(Color.blue, lineWidth: 3)
.frame(width: 220, height: 220)
}
.padding(.bottom, 180)

Spacer()

// ✅ 직접 등록 버튼
Button(action: {
dismiss()
}) {
Text("직접 등록 하기")
.fontWeight(.semibold)
.foregroundColor(.black)
.frame(maxWidth: .infinity)
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(color: .gray.opacity(0.3), radius: 2, x: 0, y: 2)
}
.padding(.horizontal, 40)
.padding(.bottom, 40)
}
.padding(.top, 20)

Spacer()

// 직접 등록 버튼
Button(action: {
print("직접 등록 tapped")
}) {
Text("직접 등록 하기")
.fontWeight(.semibold)
.foregroundColor(.black)
.frame(maxWidth: .infinity)

// ✅ 로딩 표시
if orderViewModel.isLoading {
Color.black.opacity(0.3).ignoresSafeArea()
ProgressView("입고 처리 중...")
.padding()
.background(Color.white)
.background(.ultraThinMaterial)
.cornerRadius(10)
.shadow(color: .gray.opacity(0.3), radius: 2, x: 0, y: 2)
}
.padding(.horizontal, 40)
.padding(.bottom, 40)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray.opacity(0.1))
.alert("입고 처리 결과", isPresented: $showAlert) {
Button("확인") {
dismiss()
}
} message: {
Text(alertMessage)
}
.onChange(of: scannedCode) { newValue in
guard let code = newValue, !code.isEmpty else { return }
Task {
await handleScannedCode(code)
}
}
.navigationTitle("입고 부품 등록")
.navigationBarTitleDisplayMode(.inline)
}
}

#Preview {
NavigationStack {
IncomingScanView()

private func handleScannedCode(_ code: String) async {
await MainActor.run {
orderViewModel.isLoading = true
}

let result = await orderViewModel.receiveOrder(orderNumber: code)
await MainActor.run {
orderViewModel.isLoading = false
switch result {
case .success(let message):
alertMessage = message
case .failure(let error):
alertMessage = error.message
}
showAlert = true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ struct GridMenuView: View {
("재고 조회", true, "InvStock", AnyView(InventorySearchView())),
("입출고 히스토리", false, "InvTrans", AnyView(IncomingScanView())),
("입고 처리", false, "InvIncoming", AnyView(IncomingScanView())),
("사용 처리", true, "InvUse", AnyView(IncomingScanView())),
("사용 처리", true, "InvUse", AnyView(OutgoingScanView())),
]

var body: some View {
Expand Down
120 changes: 120 additions & 0 deletions StockMate/StockMate/app/feature/inventory/ui/OutgoingScanView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//
// OutgoingScanView.swift
// StockMate
//
// Created by Admin on 10/31/25.
//


import SwiftUI

struct OutgoingScanView: View {
@Environment(\.dismiss) private var dismiss
@State private var scannedCode: String? = nil
@State private var showAlert = false
@State private var alertMessage = ""

@StateObject private var partViewModel = PartViewModel() // ✅ ViewModel 추가

var body: some View {
ZStack {
// ✅ 카메라 미리보기 (QR 스캐너)
QRScannerView(scannedCode: $scannedCode)
.ignoresSafeArea()

// ✅ 스캔 가이드 및 UI 오버레이
VStack {
Text("사용할 부품의 QR을 스캔해주세요")
.font(.headline)
.padding(.top, 60)
.foregroundColor(.white)
.shadow(radius: 2)

Spacer()

// 📷 스캔 박스
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(Color.clear)
.frame(width: 250, height: 250)

RoundedRectangle(cornerRadius: 8)
.stroke(Color.green, lineWidth: 3)
.frame(width: 220, height: 220)
}
.padding(.bottom, 180)

Spacer()

// 📦 직접 입력 버튼
Button(action: {
dismiss()
}) {
Text("직접 입력 하기")
.fontWeight(.semibold)
.foregroundColor(.black)
.frame(maxWidth: .infinity)
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(color: .gray.opacity(0.3), radius: 2, x: 0, y: 2)
}
.padding(.horizontal, 40)
.padding(.bottom, 40)
}

// ✅ 로딩 인디케이터
if partViewModel.isLoading {
Color.black.opacity(0.3).ignoresSafeArea()
ProgressView("부품 사용 처리 중...")
.padding()
.background(.ultraThinMaterial)
.cornerRadius(10)
}
}
// ✅ 알림창
.alert("부품 사용 결과", isPresented: $showAlert) {
Button("확인") {
dismiss()
}
} message: {
Text(alertMessage)
}
// ✅ QR 스캔 이벤트 발생 시
.onChange(of: scannedCode) { newValue in
guard let code = newValue, !code.isEmpty else { return }
Task {
await handleScannedCode(code)
}
}
.navigationTitle("부품 사용 처리")
.navigationBarTitleDisplayMode(.inline)
}

// ✅ 스캔된 코드로 출고 API 호출
private func handleScannedCode(_ code: String) async {
await MainActor.run {
partViewModel.isLoading = true
}

let request = [ReleaseItemRequest(partCode: code, quantity: 1)] // 기본 1개로 설정
let result = await partViewModel.releaseParts(items: request)

await MainActor.run {
partViewModel.isLoading = false
switch result {
case .success(let message):
alertMessage = message
case .failure(let error):
alertMessage = error.message
}
showAlert = true
}
}
}

#Preview {
NavigationStack {
OutgoingScanView()
}
}
Loading