diff --git a/AuthService.swift b/AuthService.swift index 125bafe..4140800 100644 --- a/AuthService.swift +++ b/AuthService.swift @@ -8,30 +8,32 @@ import Foundation import Firebase - - -class AuthService{ +class AuthService: ObservableObject { @Published var userSession: FirebaseAuth.User? static let shared = AuthService() - init(){ + + private init() { self.userSession = Auth.auth().currentUser } + @MainActor - func login(withEmail email: String, password: String) async throws{ + func login(withEmail email: String, password: String) async throws { do { let result = try await Auth.auth().signIn(withEmail: email, password: password) self.userSession = result.user } catch { print("DEBUG: Failed to login user with error \(error.localizedDescription)") } - } - - func signout(){ - try? Auth.auth().signOut() - self.userSession = nil + func signout() { + do { + try Auth.auth().signOut() + self.userSession = nil + } catch { + print("DEBUG: Failed to sign out with error \(error.localizedDescription)") + } } } diff --git a/ContentView.swift b/ContentView.swift index 24dbf13..0b250da 100644 --- a/ContentView.swift +++ b/ContentView.swift @@ -11,7 +11,7 @@ struct ContentView: View { @StateObject var viewModel = ContentViewModel() var body: some View { - Group{ + Group { if viewModel.userSession == nil { Login() } else { @@ -21,6 +21,7 @@ struct ContentView: View { } } +// Preview for SwiftUI canvas #Preview { ContentView() } diff --git a/ContentViewModel.swift b/ContentViewModel.swift new file mode 100644 index 0000000..99a4f2b --- /dev/null +++ b/ContentViewModel.swift @@ -0,0 +1,28 @@ +// +// ContentView.swift +// Industrial_APP +// +// Created by User on 2023/12/29. +// + +import Foundation +import Firebase +import Combine + +@MainActor +class ContentViewModel: ObservableObject{ + + private let service = AuthService.shared + private var cancellables = Set() + + @Published var userSession : FirebaseAuth.User? + init(){ + setupSubscribers() + } + func setupSubscribers(){ + service.$userSession.sink{[weak self] userSession in + self?.userSession = userSession + } + .store(in: &cancellables) + } +} diff --git a/Cost.swift b/Cost.swift index b365a01..ef1d8f3 100644 --- a/Cost.swift +++ b/Cost.swift @@ -1,3 +1,10 @@ +// +// ContentView.swift +// Industrial_APP +// +// Created by User on 2023/5/17. +// + import SwiftUI struct Cost: View { diff --git a/IE.swift b/IE.swift index e83bda3..09d4250 100644 --- a/IE.swift +++ b/IE.swift @@ -1,201 +1,141 @@ +// +// ContentView.swift +// Industrial_APP +// +// Created by User on 2023/2/21. +// + import SwiftUI import FirebaseFirestore -struct Process: Identifiable, Codable { - var id: UUID // 使用 UUID 作為唯一 ID - var productName: String +struct ProcessData: Identifiable, Codable { + var id: String + var name: String var quantity: String var machine: String var progress: String } struct IE: View { - @State private var searchText = "" - @State private var productionOrder: [Process] = [] - @State private var isSheetPresented = false + @State private var processes: [ProcessData] = [] @State private var showAlert = false - @State private var deleteIndexSet: IndexSet? - - var filteredProductionOrder: [Process] { - if searchText.isEmpty { - return productionOrder - } else { - return productionOrder.filter { process in - process.productName.lowercased().contains(searchText.lowercased()) || - process.quantity.lowercased().contains(searchText.lowercased()) || - process.machine.lowercased().contains(searchText.lowercased()) || - process.progress.lowercased().contains(searchText.lowercased()) - } - } - } + @State private var deleteProcess: ProcessData? + private let collectionReference = Firestore.firestore().collection("processes") var body: some View { NavigationView { VStack { List { - ForEach(filteredProductionOrder) { process in - VStack() { - /* Text("ID: \(process.id)") - .frame(maxWidth: .infinity, alignment: .leading) - .font(.title2)*/ - Text("Producto: \(process.productName)") - .frame(maxWidth: .infinity, alignment: .leading) - .font(.title2) - Text("Cantidad: \(process.quantity)") - .frame(maxWidth: .infinity, alignment: .leading) - .font(.title2) - Text("Máquina: \(process.machine)") - .frame(maxWidth: .infinity, alignment: .leading) - .font(.title2) - Text("Progreso: \(process.progress)") - .frame(maxWidth: .infinity, alignment: .leading) - .font(.title2) + HeaderRow() + + ForEach($processes) { $process in + HStack { + TextField("Producto", text: $process.name, onCommit: { + saveProcess(process) + }) + TextField("Cantidad", text: $process.quantity, onCommit: { + saveProcess(process) + }) + TextField("Máquina", text: $process.machine, onCommit: { + saveProcess(process) + }) + TextField("Progreso", text: $process.progress, onCommit: { + saveProcess(process) + }) + + Button(action: { + deleteProcess = process + showAlert = true + }) { + Image(systemName: "trash") + .foregroundColor(.red) + } } } - .onDelete(perform: { indexSet in - deleteIndexSet = indexSet - showAlert.toggle() - }) // Enable row deletion - } - .searchable(text: $searchText, prompt: "Buscar...") - - Spacer() } .navigationBarTitle("Proceso", displayMode: .inline) .navigationBarItems(trailing: Button(action: { - isSheetPresented.toggle() + addProcess() }) { Image(systemName: "plus") - } - .sheet(isPresented: $isSheetPresented) { - AddProcessView(productionOrder: $productionOrder, isSheetPresented: $isSheetPresented) }) + .onAppear { + fetchProcesses() + } .alert(isPresented: $showAlert) { Alert( - title: Text("¡Advertencia!"), - message: Text("¿Estás seguro de eliminar este proceso?"), - primaryButton: .destructive(Text("Eliminar")) { - // Perform deletion action here - if let indexSet = deleteIndexSet { - deleteRow(at: indexSet) + title: Text("Confirm Delete"), + message: Text("Are you sure you want to delete this row?"), + primaryButton: .destructive(Text("Delete")) { + if let processToDelete = deleteProcess { + deleteProcess(processToDelete) } }, - secondaryButton: .cancel(Text("Cancelar")) + secondaryButton: .cancel() ) } } - .onAppear { - fetchFromFirestore() - } } - // Function to delete a row - private func deleteRow(at indexSet: IndexSet) { - // 获取要删除的文档的 ID - let idsToDelete = indexSet.compactMap { productionOrder[$0].id.uuidString } - productionOrder.remove(atOffsets: indexSet) - - // 删除对应的 Firebase 文档 - let db = Firestore.firestore() - let ordersCollection = db.collection("process") - - idsToDelete.forEach { id in - ordersCollection.whereField("id", isEqualTo: id).getDocuments { snapshot, error in - if let error = error { - print("Error fetching documents: \(error)") - return - } - guard let documents = snapshot?.documents else { - print("No documents found") - return - } - for document in documents { - document.reference.delete { error in - if let error = error { - print("Error removing document: \(error)") - } else { - print("Document successfully removed from Firestore") - } - } - } - } + private func HeaderRow() -> some View { + HStack { + Text("Producto").font(.footnote).frame(maxWidth: .infinity, alignment: .center) + Text("Cantidad").font(.footnote).frame(maxWidth: .infinity, alignment: .center) + Text("Máquina").font(.footnote).frame(maxWidth: .infinity, alignment: .center) + Text("Progreso").font(.footnote).frame(maxWidth: .infinity, alignment: .center) } } - // Function to fetch data from Firestore - private func fetchFromFirestore() { - let db = Firestore.firestore() - let collectionRef = db.collection("process") - - collectionRef.getDocuments { snapshot, error in + + private func fetchProcesses() { + collectionReference.getDocuments { snapshot, error in if let error = error { - print("Error getting documents: \(error)") - return - } - - guard let documents = snapshot?.documents else { - print("No documents") + print("Error fetching documents: \(error)") return } - productionOrder = documents.compactMap { document in - var process = try? document.data(as: Process.self) - process?.id = UUID(uuidString: document.documentID) ?? UUID() // 將 Firestore 文檔 ID 賦值給 process 的 id 屬性 - return process + guard let documents = snapshot?.documents else { return } + processes = documents.compactMap { document in + do { + let processData = try document.data(as: ProcessData.self) + return processData + } catch { + print("Error decoding document: \(error)") + return nil + } } } } -} -struct AddProcessView: View { - @Binding var productionOrder: [Process] - @State private var newProcess = Process(id: UUID(), productName: "", quantity: "", machine: "", progress: "") - @Binding var isSheetPresented: Bool - @Environment(\.presentationMode) var presentationMode + private func saveProcess(_ process: ProcessData) { + do { + try collectionReference.document(process.id).setData(from: process) + } catch { + print("Error saving document: \(error)") + } + } - var body: some View { - NavigationView { - VStack { - Form { - Section { - TextField("Nombre del producto", text: $newProcess.productName) - TextField("Cantidad", text: $newProcess.quantity) - TextField("Máquina", text: $newProcess.machine) - TextField("Progreso", text: $newProcess.progress) - } + private func deleteProcess(_ process: ProcessData) { + collectionReference.document(process.id).delete { error in + if let error = error { + print("Error deleting document: \(error)") + } else { + if let index = processes.firstIndex(where: { $0.id == process.id }) { + processes.remove(at: index) } - .navigationBarTitle("Agregar proceso") - .navigationBarItems( - leading: Button("Cancelar") { - presentationMode.wrappedValue.dismiss() - }.foregroundColor(.red), - trailing: Button("Ahorrar", action: saveOrder) - ) } } } - - private func saveOrder() { - let db = Firestore.firestore() - let ordersCollection = db.collection("process") - - do { - let documentReference = try ordersCollection.addDocument(from: newProcess) - let documentID = documentReference.documentID - print("Document added with ID: \(documentReference.documentID)") - - // After successful save, add the new order to local data - productionOrder.append(newProcess) - isSheetPresented = false - } catch { - print("Error adding document: \(error)") - } + private func addProcess() { + let newProcess = ProcessData(id: UUID().uuidString, name: "", quantity: "", machine: "", progress: "") + processes.insert(newProcess, at: 0) + saveProcess(newProcess) } } - struct IE_Previews: PreviewProvider { - static var previews: some View { - IE() - } - } +struct IE_Previews: PreviewProvider { + static var previews: some View { + IE() + } +} diff --git a/Industrial_APP.xcodeproj.zip b/Industrial_APP.xcodeproj.zip index 4fdc6c0..a25fc12 100644 Binary files a/Industrial_APP.xcodeproj.zip and b/Industrial_APP.xcodeproj.zip differ diff --git a/Industrial_APPApp.swift b/Industrial_APPApp.swift index 1fdb302..54a2ba9 100644 --- a/Industrial_APPApp.swift +++ b/Industrial_APPApp.swift @@ -8,18 +8,22 @@ import SwiftUI import FirebaseCore +// AppDelegate class to handle Firebase configuration during app launch class AppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - FirebaseApp.configure() - - return true - } + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + FirebaseApp.configure() + return true + } } +// Main entry point for the app @main struct Industrial_APPApp: App { + // Connect the AppDelegate for handling app lifecycle events @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate + + // Define the main scene for the app var body: some Scene { WindowGroup { ContentView() diff --git a/Inventory.swift b/Inventory.swift index d69dfb6..9cc85de 100644 --- a/Inventory.swift +++ b/Inventory.swift @@ -83,7 +83,7 @@ struct InventoryView: View { TextField("Nonexistence", text: $row.nonexistence, onCommit: { self.saveRow(row) }) - .frame(maxWidth: .infinity, alignment: .leading) // Align the content to the leading edge + .frame(maxWidth: .infinity, alignment: .leading) .textFieldStyle(RoundedBorderTextFieldStyle()) @@ -92,24 +92,23 @@ struct InventoryView: View { self.showAlert = true }) { Image(systemName: "trash") + .foregroundColor(.red) } } } } - Spacer() // Push "Add" button to the bottom - - Button(action: { - self.addRow() - }) { - HStack { - Image(systemName: "plus") - Text("Add") - } - } + Spacer() } .navigationBarTitle("Inventario", displayMode: .inline) + .navigationBarItems(trailing: Button(action: { + self.addRow() + }) { + HStack { + Image(systemName: "plus") + } + }) .onAppear { self.fetchRows() } @@ -156,7 +155,6 @@ struct InventoryView: View { if let error = error { print("Error deleting document: \(error)") } else { - // If deletion from Firebase is successful, remove the corresponding row from the UI if let index = self.rows.firstIndex(where: { $0.id == row.id }) { self.rows.remove(at: index) } diff --git a/Login.swift b/Login.swift index bd69381..7f2e6d1 100644 --- a/Login.swift +++ b/Login.swift @@ -10,7 +10,7 @@ import Firebase struct Login: View { @StateObject var viewModel = LoginViewModel() - @State private var isLoggedIn = false // 新增一個狀態來表示是否已登入 + @State private var isLoggedIn = false var body: some View { NavigationView { @@ -31,10 +31,9 @@ struct Login: View { Task { do { try await viewModel.signin() - isLoggedIn = true // 登入成功後設置為 true + isLoggedIn = true } catch { print("Error: \(error.localizedDescription)") - // 登入失敗,顯示相應的錯誤消息 } } } label: { @@ -53,7 +52,7 @@ struct Login: View { .fullScreenCover(isPresented: $isLoggedIn, content: { MainTabView() }) - .navigationBarBackButtonHidden(true) // 隱藏返回按鈕 + .navigationBarBackButtonHidden(true) } } @@ -70,13 +69,10 @@ class LoginViewModel: ObservableObject { throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Password is required"]) } - // 驗證帳號密碼 let result = try await Auth.auth().signIn(withEmail: email, password: password) - // 如果驗證成功,result.user 將包含用戶信息 } } -// 修改預覽 struct Login_Previews: PreviewProvider { static var previews: some View { Login() diff --git a/Machine.swift b/Machine.swift index f802823..773890d 100644 --- a/Machine.swift +++ b/Machine.swift @@ -3,14 +3,7 @@ // Industrial_APP // // Created by User on 2023/12/29. -//1/14停機紅匡警示 正常要用綠色 -//每個按鈕按進去要連接到不同機台頁面要怎麼設計(不然總共有32個頁面) -//次數連接資料庫 -//機台故障維修警示 -//2/19 跟不同機器故障原因分儲存、別讀取firebase 數據並顯示在UI -//2/20 toggle更新 - - +// import Foundation import SwiftUI @@ -66,7 +59,6 @@ struct MachineControl: View { Machine(machineID: "16", name: "雷射雕刻機 \n LASER", description: "Description 16", isRunning: UserDefaultsManager.shared.getMachineStatus("16")), ] - var body: some View { NavigationView { ScrollView { @@ -74,11 +66,11 @@ struct MachineControl: View { ForEach(machines) { machine in NavigationLink(destination: MachineDetail( isRunning: $machines[machineIndex(machine)].isRunning, - machine: $machines[machineIndex(machine)], machineID: machine.machineID - )){ + machine: $machines[machineIndex(machine)], + machineID: machine.machineID + )) { MachineButton(machine: machine) } - .padding() } } @@ -107,6 +99,7 @@ struct MachineButton: View { .foregroundColor(.black) .truncationMode(.tail) .padding(8) + .frame(maxWidth: .infinity, maxHeight: .infinity) .background( RoundedRectangle(cornerRadius: 8) .stroke(machine.isRunning ? Color.green : Color.red, lineWidth: 5) @@ -115,65 +108,66 @@ struct MachineButton: View { } struct MachineDetail: View { - struct ReasonItem: Identifiable { var id = UUID() var reason: String - var machineID: String // 機台的唯一標識符 - var reasonID: String // 故障原因的唯一標識符 + var machineID: String + var reasonID: String var count: Int } + @Binding var isRunning: Bool @Binding var machine: Machine @State private var reasons: [ReasonItem] = [ ReasonItem(reason: "Operación incorrecta", machineID: "", reasonID: "1", count: 0), ReasonItem(reason: "Equipo desgastado", machineID: "", reasonID: "2", count: 0), - ReasonItem(reason: "Falta de material", machineID: "", reasonID: "3", count:0 ), + ReasonItem(reason: "Falta de material", machineID: "", reasonID: "3", count: 0), ReasonItem(reason: "Otros", machineID: "", reasonID: "4", count: 0), ] + @Environment(\.dismiss) var dismiss - let machineID: String // 這裡將機器ID改為String類型 + let machineID: String var body: some View { + VStack { + Chart(reasons) { reason in + SectorMark( + angle: .value(Text(verbatim: reason.reason), reason.count), + innerRadius: .ratio(0.618), + angularInset: 1.5 + ) + .foregroundStyle(by: .value( + Text(verbatim: reason.reason), + String(reason.reason) + )) + } + .frame(width: 350, height: 300) + VStack { - Chart(reasons) { reason in - SectorMark( - angle: .value(Text(verbatim: reason.reason), reason.count), - innerRadius: .ratio(0.618), angularInset: 1.5 - ) - .foregroundStyle(by: .value( - Text(verbatim: reason.reason), - String(reason.reason) - )) - } - .frame(width: 350, height: 300) // 設定圖表的寬度和高度 - - VStack { - List { - HStack { - Text("Razón") - .frame(maxWidth: .infinity, alignment: .leading) - } - .listRowBackground(Color.gray) // 整個 HStack 背景色 - - ForEach(reasons, id: \.reason) { item in - NavigationLink(destination: ReasonRecord(machineID: machineID, reasonID: item.reasonID)) { - Text(item.reason) - .font(.callout) - .lineLimit(2) - .foregroundColor(.black) // 設置文字顏色為黑色 - .truncationMode(.tail) - } + List { + HStack { + Text("Razón") + .frame(maxWidth: .infinity, alignment: .leading) + } + .listRowBackground(Color.gray) + + ForEach(reasons, id: \.reason) { item in + NavigationLink(destination: ReasonRecord(machineID: machineID, reasonID: item.reasonID)) { + Text(item.reason) + .font(.callout) + .lineLimit(2) + .foregroundColor(.black) + .truncationMode(.tail) } } - .listStyle(PlainListStyle()) // 移除 List 的背景色 } - Toggle("Estado", isOn: $isRunning) - .padding() + .listStyle(PlainListStyle()) } - .navigationTitle("Analisis fallido") // 設置標題 - .navigationBarTitleDisplayMode(.inline) // 設置標題顯示模式 - + Toggle("Estado", isOn: $isRunning) + .padding() + } + .navigationTitle("Analisis fallido") + .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .navigationBarLeading) { @@ -187,38 +181,36 @@ struct MachineDetail: View { .onDisappear { UserDefaultsManager.shared.saveMachineStatus(machine.machineID, isRunning: machine.isRunning) } - .onAppear { - fetchReasonCounts() -} + .onAppear { + fetchReasonCounts() + } } -func fetchReasonCounts() { - let db = Firestore.firestore() - let collectionRef = db.collection("Machine_Diary") - - // Loop through each reason item to fetch count - for i in 0.. some View { + private var headerRow: some View { HStack { Text("Cliente").font(.footnote).frame(maxWidth: .infinity, alignment: .center) Text("Fecha").font(.footnote).frame(maxWidth: .infinity, alignment: .center) @@ -91,7 +86,7 @@ struct Order: View { } guard let documents = snapshot?.documents else { return } - self.orders = documents.compactMap { document in + orders = documents.compactMap { document in do { let orderData = try document.data(as: OrderData.self) return orderData @@ -116,8 +111,8 @@ struct Order: View { if let error = error { print("Error deleting document: \(error)") } else { - if let index = self.orders.firstIndex(where: { $0.id == order.id }) { - self.orders.remove(at: index) + if let index = orders.firstIndex(where: { $0.id == order.id }) { + orders.remove(at: index) } } } @@ -130,6 +125,18 @@ struct Order: View { } } +struct OrderTextField: View { + let placeholder: String + @Binding var text: String + var onCommit: () -> Void + + var body: some View { + TextField(placeholder, text: $text, onCommit: onCommit) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .padding(.horizontal, 4) + } +} + struct Order_Previews: PreviewProvider { static var previews: some View { Order() diff --git a/Sop.swift b/Sop.swift index 99bec74..83524a5 100644 --- a/Sop.swift +++ b/Sop.swift @@ -1,10 +1,17 @@ +// +// ContentView.swift +// Industrial_APP +// +// Created by User on 2023/4/30. +// + import SwiftUI import PDFKit struct Sop: View { var body: some View { PDFKitView(pdfName: "sop") - .aspectRatio(contentMode: .fit) // 使用fit適應螢幕大小 + .aspectRatio(contentMode: .fit) } } @@ -15,17 +22,16 @@ struct PDFKitView: UIViewRepresentable { fatalError("Could not find PDF file named \(pdfName).pdf") } let url = URL(fileURLWithPath: path) - let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) // 設置 PDFView 的 frame + let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) pdfView.document = PDFDocument(url: url) - pdfView.autoScales = true // 设置自动缩放以适应视图大小 - pdfView.displayMode = .singlePageContinuous // 設置顯示模式為單頁連續 - pdfView.maxScaleFactor = 4.0 // 設置最大縮放倍率 + pdfView.autoScales = true + pdfView.displayMode = .singlePageContinuous + pdfView.maxScaleFactor = 4.0 pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit return pdfView } func updateUIView(_ uiView: PDFView, context: Context) { - // Update code when needed } } diff --git a/sop.pdf b/sop.pdf new file mode 100644 index 0000000..dfebfeb Binary files /dev/null and b/sop.pdf differ