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
91 changes: 91 additions & 0 deletions StockMate/StockMate/app/core/components/InventoryCardView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// InventoryCardView.swift
// StockMate
//
// Created by Admin on 10/16/25.
//

import SwiftUI


struct InventoryCardView: View {
let item: InventoryItem
var showStatus: Bool = true // 필요 시 상태 표시 숨기기 옵션

var body: some View {
VStack(alignment: .leading, spacing: 5) {
// 상단 카테고리
Text(item.categoryName)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.black)

Divider()
.frame(height: 0.2)
.background(Color.textGray2)

HStack(alignment: .center, spacing: 12) {
// 제품 이미지
AsyncImage(url: URL(string: item.image)) { image in
image.resizable().scaledToFit()
} placeholder: {
Color.gray.opacity(0.2)
}
.frame(width: 64, height: 64)
.cornerRadius(10)

// 제품명, 트림/모델
VStack(alignment: .leading, spacing: 6) {
HStack {
Text(item.korName)
.font(.system(size: 14, weight: .bold))
.foregroundColor(.black)
.lineLimit(2)
Spacer()
}
.padding(.top, 2)

Text("\(item.trim) / \(item.model)")
.font(.system(size: 13, weight: .regular))
.foregroundColor(.gray)
}
.frame(height: 60, alignment: .top)

// 상태 표시 (옵션)
if showStatus {
VStack(alignment: .center, spacing: 6) {
if item.isLack {
Text("수량 부족")
.font(.system(size: 13, weight: .regular))
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(Color.DangerBg)
.foregroundColor(.Danger)
.cornerRadius(12)
} else {
Text("수량 여유")
.font(.system(size: 13, weight: .regular))
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(Color.StatusGreenBg)
.foregroundColor(.StatusGreen)
.cornerRadius(12)
}

VStack(alignment: .leading) {
Text("현재수량: \(item.amount)개")
.font(.system(size: 11, weight: .regular))
.foregroundColor(.textGray1)
Text("최소수량: \(item.limitAmount)개")
.font(.system(size: 11, weight: .regular))
.foregroundColor(.textGray1)
}
}
}
}
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(color: .black.opacity(0.05), radius: 4, x: 0, y: 4)
}
}
18 changes: 18 additions & 0 deletions StockMate/StockMate/app/core/components/InventoryListSection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// InventoryListSection.swift
// StockMate
//
// Created by Admin on 10/16/25.
//

import SwiftUI

struct InventoryListSection: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}

#Preview {
InventoryListSection()
}
1 change: 0 additions & 1 deletion StockMate/StockMate/app/feature/auth/ui/LoginView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ struct LoginView: View {
@State private var emailError: String? = nil
@State private var pwError: String? = nil

// var onLoginSuccess: () -> Void = {}
var onLogin: (String, String) -> Void = { _, _ in }
var onClickRegister: () -> Void = {}

Expand Down
83 changes: 83 additions & 0 deletions StockMate/StockMate/app/feature/inventory/data/InventoryApi.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// InventoryApi.swift
// StockMate
//
// Created by Admin on 10/15/25.
//


import Foundation
import Alamofire

struct InventoryResponse: Decodable {
let status: Int
let success: Bool
let message: String
let data: InventoryPageData?
}

struct InventoryPageData: Decodable {
let content: [InventoryItem]
let page: Int
let size: Int
let totalElements: Int
let totalPages: Int
}

struct InventoryItem: Decodable, Identifiable {
let id: Int
let name: String
let price: Int
let image: String
let trim: String
let model: String
let category: Int
let korName: String
let engName: String
let categoryName: String
let stock: Int // 본사 재고
let amount: Int // 지점별 재고
let limitAmount: Int // 지점별 최소 수량
let isLack: Bool
}

enum InventoryApi {
static func getInventoryList(
page: Int,
size: Int,
categoryNames: [String],
trims: [String],
models: [String]
) -> DataRequest {
var url = ApiClient.baseURL + "api/v1/store/search?page=\(page)&size=\(size)"

for c in categoryNames {
url += "&categoryName=\(c.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
}
for t in trims {
url += "&trim=\(t.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
}
for m in models {
url += "&model=\(m.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
}

return ApiClient.shared.request(url, method: .get)
}

// ✅ 부족 재고 조회 API 추가
static func getUnderLimitList(categoryName: String? = nil, page: Int = 0, size: Int = 10) -> DataRequest {
var url = ApiClient.baseURL + "api/v1/store/under-limit?page=\(page)&size=\(size)"
if let categoryName = categoryName, !categoryName.isEmpty {
url += "&categoryName=\(categoryName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
}
return ApiClient.shared.request(url, method: .get)
}

// ✅ 부품 이름으로 검색
static func findByName(name: String, page: Int, size: Int) -> DataRequest {
let encodedName = name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let url = ApiClient.baseURL + "api/v1/store/find-name?name=\(encodedName)&page=\(page)&size=\(size)"
return ApiClient.shared.request(url, method: .get)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// InventoryRepositoryImpl.swift
// StockMate
//
// Created by Admin on 10/15/25.
//

import Foundation
import Alamofire

final class InventoryRepositoryImpl: InventoryRepositoryProtocol {
func getInventoryList(
page: Int,
size: Int,
categoryNames: [String],
trims: [String],
models: [String]
) async -> AppResult<ApiResponse<InventoryPageData>> {
let dataReq = InventoryApi.getInventoryList(
page: page,
size: size,
categoryNames: categoryNames,
trims: trims,
models: models
)
return await safeApi(dataReq, decodeTo: ApiResponse<InventoryPageData>.self)
}
// 부족 재고 리스트 호출
func getUnderLimitList(
categoryName: String?,
page: Int,
size: Int
) async -> AppResult<ApiResponse<InventoryPageData>> {
let dataReq = InventoryApi.getUnderLimitList(categoryName: categoryName, page: page, size: size)
return await safeApi(dataReq, decodeTo: ApiResponse<InventoryPageData>.self)
}

// ✅ 이름 검색
func findByName(
name: String,
page: Int,
size: Int
) async -> AppResult<ApiResponse<InventoryPageData>> {
let dataReq = InventoryApi.findByName(name: name, page: page, size: size)
return await safeApi(dataReq, decodeTo: ApiResponse<InventoryPageData>.self)
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// InventoryRepositoryProtocol.swift
// StockMate
//
// Created by Admin on 10/15/25.
//

import Foundation

protocol InventoryRepositoryProtocol {
func getInventoryList(
page: Int,
size: Int,
categoryNames: [String],
trims: [String],
models: [String]
) async -> AppResult<ApiResponse<InventoryPageData>>

func getUnderLimitList(
categoryName: String?,
page: Int,
size: Int
) async -> AppResult<ApiResponse<InventoryPageData>>

// 이름 검색
func findByName(
name: String,
page: Int,
size: Int
) async -> AppResult<ApiResponse<InventoryPageData>>

}
Loading