diff --git a/ipad.xcodeproj/project.pbxproj b/ipad.xcodeproj/project.pbxproj index 27fd3ed6..556e776e 100644 --- a/ipad.xcodeproj/project.pbxproj +++ b/ipad.xcodeproj/project.pbxproj @@ -186,6 +186,12 @@ 29FFEBBE237F1EAA00574E3F /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FFEBBD237F1EAA00574E3F /* Settings.swift */; }; 29FFEBC1237F1F6F00574E3F /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FFEBC0237F1F6F00574E3F /* SettingsModel.swift */; }; 29FFEBC4237F23B800574E3F /* JWTDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FFEBC3237F23B800574E3F /* JWTDecoder.swift */; }; + 41CAEC692B44A2C5002B2960 /* BlowbyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41CAEC682B44A2C5002B2960 /* BlowbyModel.swift */; }; + 41CAEC6B2B44AB0B002B2960 /* BlowbyFormHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41CAEC6A2B44AB0B002B2960 /* BlowbyFormHelper.swift */; }; + 41CAEC712B44BF19002B2960 /* BlowbyTableCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41CAEC702B44BF19002B2960 /* BlowbyTableCollectionViewCell.swift */; }; + 41CAEC732B44C1B1002B2960 /* BlowbyTableCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 41CAEC722B44C1B1002B2960 /* BlowbyTableCollectionViewCell.xib */; }; + 41CAEC7D2B45C538002B2960 /* ShiftBlowbysHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41CAEC7C2B45C538002B2960 /* ShiftBlowbysHeaderCollectionViewCell.swift */; }; + 41CAEC7F2B45C54F002B2960 /* ShiftBlowbysHeaderCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 41CAEC7E2B45C54F002B2960 /* ShiftBlowbysHeaderCollectionViewCell.xib */; }; 5C0F6979237B635800F96CA5 /* WaterBodyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0F6978237B635800F96CA5 /* WaterBodyAPI.swift */; }; 5C0F697D237B683200F96CA5 /* CodesAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0F697C237B683200F96CA5 /* CodesAPI.swift */; }; 5C0F6980237C6DDA00F96CA5 /* APIChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0F697E237B6F7800F96CA5 /* APIChain.swift */; }; @@ -208,6 +214,8 @@ 5C82D404237B31C200B065BA /* WatercraftRiskAssessmentAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C82D403237B31C200B065BA /* WatercraftRiskAssessmentAPI.swift */; }; 9620B078298849BF0084268C /* NullSwitchInputCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9620B076298849BF0084268C /* NullSwitchInputCollectionViewCell.swift */; }; 9620B079298849BF0084268C /* NullSwitchInputCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9620B077298849BF0084268C /* NullSwitchInputCollectionViewCell.xib */; }; + 96CDB9632B4886FF009A45C7 /* NewBlowbyModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96CDB9612B4886FF009A45C7 /* NewBlowbyModal.swift */; }; + 96CDB9642B4886FF009A45C7 /* NewBlowbyModal.xib in Resources */ = {isa = PBXBuildFile; fileRef = 96CDB9622B4886FF009A45C7 /* NewBlowbyModal.xib */; }; A7AE5275237CC3660044DBB7 /* unapproved-cross.json in Resources */ = {isa = PBXBuildFile; fileRef = A7AE5272237CC3660044DBB7 /* unapproved-cross.json */; }; A7AE5276237CC3660044DBB7 /* check-mark-success.json in Resources */ = {isa = PBXBuildFile; fileRef = A7AE5273237CC3660044DBB7 /* check-mark-success.json */; }; A7AE5277237CC3660044DBB7 /* sync-circle.json in Resources */ = {isa = PBXBuildFile; fileRef = A7AE5274237CC3660044DBB7 /* sync-circle.json */; }; @@ -413,6 +421,12 @@ 29FFEBBD237F1EAA00574E3F /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 29FFEBC0237F1F6F00574E3F /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; 29FFEBC3237F23B800574E3F /* JWTDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWTDecoder.swift; sourceTree = ""; }; + 41CAEC682B44A2C5002B2960 /* BlowbyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlowbyModel.swift; sourceTree = ""; }; + 41CAEC6A2B44AB0B002B2960 /* BlowbyFormHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlowbyFormHelper.swift; sourceTree = ""; }; + 41CAEC702B44BF19002B2960 /* BlowbyTableCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlowbyTableCollectionViewCell.swift; sourceTree = ""; }; + 41CAEC722B44C1B1002B2960 /* BlowbyTableCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BlowbyTableCollectionViewCell.xib; sourceTree = ""; }; + 41CAEC7C2B45C538002B2960 /* ShiftBlowbysHeaderCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShiftBlowbysHeaderCollectionViewCell.swift; sourceTree = ""; }; + 41CAEC7E2B45C54F002B2960 /* ShiftBlowbysHeaderCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShiftBlowbysHeaderCollectionViewCell.xib; sourceTree = ""; }; 5C0F6978237B635800F96CA5 /* WaterBodyAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaterBodyAPI.swift; sourceTree = ""; }; 5C0F697A237B63F500F96CA5 /* MusselAppAPITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusselAppAPITest.swift; sourceTree = ""; }; 5C0F697C237B683200F96CA5 /* CodesAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodesAPI.swift; sourceTree = ""; }; @@ -445,6 +459,8 @@ 763C8716EF742F6764FFE7D7 /* Pods-ipad.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ipad.debug.xcconfig"; path = "Target Support Files/Pods-ipad/Pods-ipad.debug.xcconfig"; sourceTree = ""; }; 9620B076298849BF0084268C /* NullSwitchInputCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NullSwitchInputCollectionViewCell.swift; sourceTree = ""; }; 9620B077298849BF0084268C /* NullSwitchInputCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NullSwitchInputCollectionViewCell.xib; sourceTree = ""; }; + 96CDB9612B4886FF009A45C7 /* NewBlowbyModal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewBlowbyModal.swift; sourceTree = ""; }; + 96CDB9622B4886FF009A45C7 /* NewBlowbyModal.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NewBlowbyModal.xib; sourceTree = ""; }; 987374CFF5F2318652AA2FCC /* Pods-ipadTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ipadTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ipadTests/Pods-ipadTests.release.xcconfig"; sourceTree = ""; }; 99C48CD83AAD3AAA2CD083BF /* Pods-ipadTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ipadTests.release.xcconfig"; path = "Target Support Files/Pods-ipadTests/Pods-ipadTests.release.xcconfig"; sourceTree = ""; }; A41A1378A874E8BDB895F235 /* Pods-ipad.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ipad.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ipad/Pods-ipad.debug.xcconfig"; sourceTree = ""; }; @@ -592,6 +608,7 @@ children = ( 29DB4E4023870F2B00B13646 /* ShiftModel.swift */, 2944BCD92433931600826F48 /* Form Fields */, + 41CAEC682B44A2C5002B2960 /* BlowbyModel.swift */, ); path = Shift; sourceTree = ""; @@ -622,6 +639,7 @@ isa = PBXGroup; children = ( 296DEC22238C56A30035E7FA /* ShiftFormHelper.swift */, + 41CAEC6A2B44AB0B002B2960 /* BlowbyFormHelper.swift */, ); path = "Form Fields"; sourceTree = ""; @@ -676,6 +694,7 @@ 2944BCDF243394A300826F48 /* Cells */ = { isa = PBXGroup; children = ( + 41CAEC7B2B45C4AC002B2960 /* Blowbys */, 29DB4E422387111700B13646 /* Base */, 29DB4E432387112000B13646 /* Header */, 29DB4E4B238712ED00B13646 /* Inspections */, @@ -739,6 +758,7 @@ 2944BCF52437C0EA00826F48 /* Views */ = { isa = PBXGroup; children = ( + 96CDB9602B4886FF009A45C7 /* New Blowby Modal */, 2968859026030F9D00ECA183 /* Major Cities Picker */, 2907E4B52368D21D00946B3F /* Input Modal */, 29A97437236FD615006C668F /* Form */, @@ -1259,6 +1279,8 @@ 29DB4E4B238712ED00B13646 /* Inspections */ = { isa = PBXGroup; children = ( + 41CAEC702B44BF19002B2960 /* BlowbyTableCollectionViewCell.swift */, + 41CAEC722B44C1B1002B2960 /* BlowbyTableCollectionViewCell.xib */, 29DB4E4C2387131000B13646 /* InspectionsTableCollectionViewCell.swift */, 29DB4E4D2387131000B13646 /* InspectionsTableCollectionViewCell.xib */, ); @@ -1374,6 +1396,15 @@ path = "JWT Decoder"; sourceTree = ""; }; + 41CAEC7B2B45C4AC002B2960 /* Blowbys */ = { + isa = PBXGroup; + children = ( + 41CAEC7C2B45C538002B2960 /* ShiftBlowbysHeaderCollectionViewCell.swift */, + 41CAEC7E2B45C54F002B2960 /* ShiftBlowbysHeaderCollectionViewCell.xib */, + ); + path = Blowbys; + sourceTree = ""; + }; 5C82D3D42374AECC00B065BA /* High Risk Modal */ = { isa = PBXGroup; children = ( @@ -1436,6 +1467,15 @@ path = "Null Switch Input"; sourceTree = ""; }; + 96CDB9602B4886FF009A45C7 /* New Blowby Modal */ = { + isa = PBXGroup; + children = ( + 96CDB9612B4886FF009A45C7 /* NewBlowbyModal.swift */, + 96CDB9622B4886FF009A45C7 /* NewBlowbyModal.xib */, + ); + path = "New Blowby Modal"; + sourceTree = ""; + }; A7AE5271237CC3660044DBB7 /* LottieAnimations */ = { isa = PBXGroup; children = ( @@ -1546,6 +1586,7 @@ 1EF7EBF223EC8E3E003886F6 /* WaterbodyPicker.xib in Resources */, 29F29F93236B6A22004F12E7 /* SwitcherView.xib in Resources */, 5C82D3D32374A0D400B065BA /* .gitignore in Resources */, + 41CAEC732B44C1B1002B2960 /* BlowbyTableCollectionViewCell.xib in Resources */, 5C82D3D72374AECC00B065BA /* HighRiskModalView.xib in Resources */, 2944AF9C2379B243007517DC /* ViewFieldCollectionViewCell.xib in Resources */, A7AE5275237CC3660044DBB7 /* unapproved-cross.json in Resources */, @@ -1579,6 +1620,7 @@ 295051112374F54A000FBD6E /* IntegerInputCollectionViewCell.xib in Resources */, 299BF02023FCA19A001732CD /* pdfMap.pdf in Resources */, 294D8F6223847C9A0094090D /* TableRowTableViewCell.xib in Resources */, + 96CDB9642B4886FF009A45C7 /* NewBlowbyModal.xib in Resources */, 296885B526030FE900ECA183 /* MajorCityPicker.xib in Resources */, F5EB30A127B5C77400F22712 /* Main.storyboard in Resources */, A7AE5276237CC3660044DBB7 /* check-mark-success.json in Resources */, @@ -1601,6 +1643,7 @@ 5C649C922412F6E300F01AE5 /* GoogleService-Info.plist in Resources */, 1E54FFB623D783940044721F /* InputTitleCollectionViewCell.xib in Resources */, 29CE5E1D2375E48000C54F96 /* ConditionalInputCollectionViewCell.xib in Resources */, + 41CAEC7F2B45C54F002B2960 /* ShiftBlowbysHeaderCollectionViewCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1684,7 +1727,9 @@ 296DEC27238C61A80035E7FA /* IntegerStepperInputCollectionViewCell.swift in Sources */, 5C7D32F424304CFC00AFCE43 /* UIViewControllerExtensions.swift in Sources */, 29BAC5192367B34000A620F4 /* Fonts.swift in Sources */, + 96CDB9632B4886FF009A45C7 /* NewBlowbyModal.swift in Sources */, 5C82D404237B31C200B065BA /* WatercraftRiskAssessmentAPI.swift in Sources */, + 41CAEC712B44BF19002B2960 /* BlowbyTableCollectionViewCell.swift in Sources */, 5C82D3D82374AECC00B065BA /* HighRiskModalView.swift in Sources */, 29BAC5102367984000A620F4 /* GradiantView.swift in Sources */, 291F918224742FD800974D33 /* WaterbodiesService.swift in Sources */, @@ -1695,6 +1740,7 @@ 295051092374D764000FBD6E /* DoubleInputCollectionViewCell.swift in Sources */, 5C82D3DD2374B9D700B065BA /* AppRemoteAPIConst.swift in Sources */, 29F29FA1236B8670004F12E7 /* DropdownCollectionViewCell.swift in Sources */, + 41CAEC6B2B44AB0B002B2960 /* BlowbyFormHelper.swift in Sources */, 29D5866925D20E6F00FEB37E /* MajorCitiesService.swift in Sources */, 29514BD92368ADB100DC89C4 /* OptionType.swift in Sources */, 290DA9E12404622700438699 /* AwaitingAccessResponse.swift in Sources */, @@ -1747,6 +1793,7 @@ 2933DD5E238DC2150068E9DF /* CodeTableService.swift in Sources */, 2933DD67238ED4470068E9DF /* ShiftService.swift in Sources */, 29FEA0A023FF12C200F490DB /* UserRoleModel.swift in Sources */, + 41CAEC692B44A2C5002B2960 /* BlowbyModel.swift in Sources */, 29DB4E3E23870B7B00B13646 /* ShifOverviewHeaderCollectionViewCell.swift in Sources */, 29DB4E462387113900B13646 /* BaseShiftOverviewCollectionViewCell.swift in Sources */, 5C82D3DF2374B9D700B065BA /* RemoteAPIManager.swift in Sources */, @@ -1798,6 +1845,7 @@ 298BF7CC2395D45C0072AA28 /* PreviousWaterBodyModel.swift in Sources */, 5C82D3DE2374B9D700B065BA /* RemoteAPI.swift in Sources */, 5C82D3F92376036700B065BA /* WorkflowAPI.swift in Sources */, + 41CAEC7D2B45C538002B2960 /* ShiftBlowbysHeaderCollectionViewCell.swift in Sources */, 29BAC51C2367B5B500A620F4 /* RealmRequests.swift in Sources */, 2950510D2374DFF7000FBD6E /* StringExtension.swift in Sources */, ); @@ -1997,7 +2045,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 383; + CURRENT_PROJECT_VERSION = 392; DEVELOPMENT_TEAM = L796QSLV3E; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L796QSLV3E; ENABLE_BITCODE = NO; @@ -2027,7 +2075,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 383; + CURRENT_PROJECT_VERSION = 392; DEVELOPMENT_TEAM = L796QSLV3E; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L796QSLV3E; ENABLE_BITCODE = NO; diff --git a/ipad/Constants/AppRemoteAPIConst.swift b/ipad/Constants/AppRemoteAPIConst.swift index 2b7de069..44e2a824 100644 --- a/ipad/Constants/AppRemoteAPIConst.swift +++ b/ipad/Constants/AppRemoteAPIConst.swift @@ -86,6 +86,7 @@ enum EndPoints: String { case user = "/account/me" case uploads = "/uploads/report-issue" case majorCities = "/mussels/major-cities" + case blowBys = "/mussels/blow-bys" } /** @@ -124,6 +125,10 @@ struct APIURL { static let uploads: String = { return Self.baseURL + EndPoints.uploads.rawValue }() + + static let blowBys: String = { + return Self.baseURL + EndPoints.blowBys.rawValue + }() } diff --git a/ipad/Constants/StringConstants.swift b/ipad/Constants/StringConstants.swift index c2186f29..b18de6f1 100644 --- a/ipad/Constants/StringConstants.swift +++ b/ipad/Constants/StringConstants.swift @@ -59,7 +59,11 @@ struct StringConstants { static let inspections: String = "" } } - +struct BlowbyFormHeaders { + static let timeStamp = "Blowby time" + static let watercraftComplexity = "Watercraft Complexity" + static let reportedToRapp = "Reported to RAPP" +} // MARK: Shift struct ShiftFormHeaders { struct ShiftStart { diff --git a/ipad/Database/Storage.swift b/ipad/Database/Storage.swift index 06364aa2..248fe772 100644 --- a/ipad/Database/Storage.swift +++ b/ipad/Database/Storage.swift @@ -115,6 +115,14 @@ class Storage { return object } + // MARK: BlowBys + public func blowBy(withLocalId localId: String) -> BlowbyModel? { + guard let realm = try? Realm(), let object = realm.objects(BlowbyModel.self).filter("localId = %@", localId).first else { + return nil + } + return object + } + // MARK: Code Tables public func codeTable(type: CodeTableType) -> [String] { do { diff --git a/ipad/Models/Shift/BlowbyModel.swift b/ipad/Models/Shift/BlowbyModel.swift new file mode 100644 index 00000000..4b0b9614 --- /dev/null +++ b/ipad/Models/Shift/BlowbyModel.swift @@ -0,0 +1,137 @@ +// +// BlowbyModel.swift +// ipad +// +// Created by Sustainment Team on 2024-01-02. +// Copyright © Sustainment Team. All rights reserved. +// + +import Foundation + +import Realm +import RealmSwift + +/// Model for displaying Blowbys that occur during a shift, complies with Realm protocols +class BlowbyModel: Object, BaseRealmObject { + @objc dynamic var userId: String = "" + @objc dynamic var localId: String = { + return UUID().uuidString + }() + + override class func primaryKey() -> String? { + return "localId" + } + + @objc dynamic var remoteId: Int = -1 + @objc dynamic var shouldSync: Bool = true + + @objc dynamic var date: Date = Date() + @objc dynamic var timeStamp: String = "" + + @objc dynamic var blowByTime: String = "" + @objc dynamic var watercraftComplexity: String = "" + @objc dynamic var reportedToRapp: Bool = false + @objc dynamic var formattedReporttoRapp: String = "No" + + // MARK: Setters + + /// Setter method for BlowbyModel, used when new Blowbys are created, as they are not Realm objects + /// - Parameters: + /// - value: value to change Models key to + /// - key: Text representation of the key to be changed in the model + func set(value: Any, for key: String) { + if self[key] == nil { + print("\(key) is nil") + return + } + self[key] = value + if key == "reportedToRapp" { + if let reported = value as? Bool { + formattedReporttoRapp = reported ? "Yes" : "No"; + } + } + } + + /// Setter method for BlowbyModel, used when editing a new Blowby, since Realm objects must be edited in a write transaction + /// - Parameters: + /// - value: Value to change Models key to + /// - key: Text representation of the key to be changed in the model + func editSet(value: Any, for key: String) { + do { + let realm = try Realm() + try realm.write { + self[key] = value; + if key == "reportedToRapp" { + if let reported = value as? Bool { + formattedReporttoRapp = reported ? "Yes" : "No"; + } + } + } + } catch let error as NSError { + print("** REALM ERROR") + print(error) + } + } + + /// Function for setting RemoteID of Object managed by Realm + /// - Parameter remoteId: remoteID Integer + func set(remoteId: Int) { + do { + let realm = try Realm() + try realm.write { + self.remoteId = remoteId + } + } catch let error as NSError { + print("** REALM ERROR") + print(error) + } + } + + /// Returns Blowby fields set to this instance of object + /// - Parameters: + /// - editable: Boolean for determining if values can be edited, or remain static + /// - modalSize: Boolean to determine if Large or small size of input fields are returned + /// - Returns: Array of InputItems tailored to this model instance + func getThisBlowbyFields(editable: Bool, modalSize: Bool) -> [InputItem] { + return BlowByFormHelper.getBlowByFields(for: self, editable: editable, modalSize: modalSize) + } + + /// Creates a formatted Date time object for displaying Blowby Data + /// - Parameters: + /// - time: Value from the TimeInput field + /// - date: Value from the Date Input field + /// - Returns: Date object taking the calendar date from date object, and time value from time String + func formattedDateTime(time: String, date: Date) -> String? { + let timeFormatter = DateFormatter() + timeFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss" + timeFormatter.timeZone = TimeZone(abbreviation: "UTC") + let startDate = date + let startTimeSplit = time.components(separatedBy: ":") + guard let timeInDate = startDate.setTime(hour: Int(startTimeSplit[0]) ?? 0, min: Int(startTimeSplit[1]) ?? 0, sec: 1) else { + return nil + } + + return timeFormatter.string(from: timeInDate) + } + + // MARK: - To Dictionary + func toDictionary() -> [String : Any] { + return toDictionary(shift: -1) + } + + func toDictionary(shift id: Int) -> [String : Any] { + let date = self.date + guard let blowByTimeFormatted = formattedDateTime(time: timeStamp, date: date) else { + return [String : Any]() + } + + let body: [String: Any] = [ + "observerWorkflowId": id, + "blowByTime": blowByTimeFormatted, + "watercraftComplexity": watercraftComplexity, + "reportedToRapp": reportedToRapp + ] + + return body + } +} diff --git a/ipad/Models/Shift/Form Fields/BlowbyFormHelper.swift b/ipad/Models/Shift/Form Fields/BlowbyFormHelper.swift new file mode 100644 index 00000000..13f148d5 --- /dev/null +++ b/ipad/Models/Shift/Form Fields/BlowbyFormHelper.swift @@ -0,0 +1,65 @@ +// +// BlowByFormHelper.swift +// ipad +// +// Created by Sustainment Team on 2023-12-29. +// Copyright © 2023 Sustainment Team. All rights reserved. +// + +import Foundation + +class BlowByFormHelper { + /// Form Headers for Blowby information in a shift + /// - Parameters: + /// - object: Model being used + /// - editable: Should the data display in a editable or static format? + /// - modalSize: Will be used in a scaled down + /// - Returns: [InputItems] + static func getBlowByFields(for object: BlowbyModel? = nil, editable: Bool? = true, modalSize: Bool? = false) -> [InputItem] { + var sectionItems: [InputItem] = [] + let timeStamp = TimeInput( + key: "timeStamp", + header: BlowbyFormHeaders.timeStamp, + editable: editable ?? true, + value: object?.timeStamp, + width: .Third + ); + sectionItems.append(timeStamp); + + let watercraftComplexity = DropdownInput ( + key: "watercraftComplexity", + header: BlowbyFormHeaders.watercraftComplexity, + editable: editable ?? true, + value: object?.watercraftComplexity, + width: .Third, + dropdownItems: [ + DropdownModel(display: "Non-motorized", key: "Non-motorized"), + DropdownModel(display: "Simple", key: "Simple"), + DropdownModel(display: "Complex", key: "Complex"), + DropdownModel(display: "Very Complex", key: "Very Complex") + ] + ); + sectionItems.append(watercraftComplexity); + + let reportedToRapp = SwitchInput( + key: "reportedToRapp", + header: BlowbyFormHeaders.reportedToRapp, + editable: editable ?? true, + value: object?.reportedToRapp, + width: .Third + ); + sectionItems.append(reportedToRapp) + + return sectionItems; + } + + /// Column configuration for Blow By table + /// - Returns: [TableViewColumnConfig] + func getTableColumns() -> [TableViewColumnConfig] { + var columns: [TableViewColumnConfig] = [] + columns.append(TableViewColumnConfig(key: "timeStamp", header: BlowbyFormHeaders.timeStamp, type: .Normal)); + columns.append(TableViewColumnConfig(key: "watercraftComplexity", header: BlowbyFormHeaders.watercraftComplexity, type: .Normal)); + columns.append(TableViewColumnConfig(key: "reportedToRapp", header: BlowbyFormHeaders.reportedToRapp, type: .Normal)); + return columns; + } +} diff --git a/ipad/Models/Shift/Form Fields/ShiftFormHelper.swift b/ipad/Models/Shift/Form Fields/ShiftFormHelper.swift index 2420f850..d2f4cb62 100644 --- a/ipad/Models/Shift/Form Fields/ShiftFormHelper.swift +++ b/ipad/Models/Shift/Form Fields/ShiftFormHelper.swift @@ -8,8 +8,16 @@ import Foundation + +/// Helper class for producing input fields for Shiftmodels class ShiftFormHelper { - + + /// Gets the required Input objects needed for the ShiftStartFields section of shift + /// - Parameters: + /// - object: Instance of the ShiftModel + /// - editable: Will forms be editable, or viewable only + /// - modalSize: Small rendering of components, or regular size rendering of components + /// - Returns: [InputItems] Form input items static func getShiftStartFields(for object: ShiftModel? = nil, editable: Bool? = true, modalSize: Bool? = false) -> [InputItem] { var sectionItems: [InputItem] = [] @@ -52,7 +60,11 @@ class ShiftFormHelper { return sectionItems } - + /// Gets the required Input objects needed for the ShiftEndFields section of shift + /// - Parameters: + /// - object: Instance of the ShiftModel + /// - editable: Will forms be editable, or viewable only + /// - Returns: [InputItems] Form input items static func getShiftEndFields(for object: ShiftModel? = nil, editable: Bool? = true) -> [InputItem] { var sectionItems: [InputItem] = [] @@ -81,29 +93,7 @@ class ShiftFormHelper { value: object?.boatsInspected, width: .Third ) - sectionItems.append(boatsInspected) - - let motorizedBlowBys = IntegerStepperInput( - key: "motorizedBlowBys", - header: ShiftFormHeaders.ShiftEnd.motorizedBlowBys, - editable: editable ?? true, - value: object?.motorizedBlowBys, - width: .Third - ) - sectionItems.append(motorizedBlowBys) - - let nonMotorizedBlowBys = IntegerStepperInput( - key: "nonMotorizedBlowBys", - header: ShiftFormHeaders.ShiftEnd.nonMotorizedBlowBys, - editable: editable ?? true, - value: object?.nonMotorizedBlowBys, - width: .Third - ) - sectionItems.append(nonMotorizedBlowBys) - - let spacer = InputSpacer() - sectionItems.append(spacer) - + sectionItems.append(boatsInspected) let shitEndComments = TextAreaInput( key: "shitEndComments", header: ShiftFormHeaders.ShiftEnd.comments, @@ -116,6 +106,9 @@ class ShiftFormHelper { return sectionItems } + + /// Return the columns for displaying Shift Overview information + /// - Returns: [TableViewColumnConfig] Array of table column configuration objects func getTableColumns() -> [TableViewColumnConfig] { // Create Column Config var columns: [TableViewColumnConfig] = [] diff --git a/ipad/Models/Shift/ShiftModel.swift b/ipad/Models/Shift/ShiftModel.swift index 2028cbe5..906b0c3e 100644 --- a/ipad/Models/Shift/ShiftModel.swift +++ b/ipad/Models/Shift/ShiftModel.swift @@ -31,15 +31,18 @@ class ShiftModel: Object, BaseRealmObject { @objc dynamic var endTime: String = "" @objc dynamic var shiftStartDate: Date = Calendar.current.startOfDay(for: Date()) @objc dynamic var boatsInspected: Bool = true + @objc dynamic var k9OnShif: Bool = false @objc dynamic var motorizedBlowBys: Int = 0 @objc dynamic var nonMotorizedBlowBys: Int = 0 - @objc dynamic var k9OnShif: Bool = false @objc dynamic var station: String = "" @objc dynamic var shitStartComments: String = "" @objc dynamic var shitEndComments: String = "" + let BlowbyFields = ["reportedToRapp", "timeStamp", "watercraftComplexity"]; var inspections: List = List() + var blowbys: List = List() @objc dynamic var status: String = "Draft" // used for query purposes (and displaying) + // used for query purposes (and displaying) @objc dynamic var formattedDate: String = "" /// Takes the Date of one Date object and combines it with the Time from another date object @@ -102,9 +105,40 @@ class ShiftModel: Object, BaseRealmObject { print(error) } } + func updateBlowbyTimeStamps(newDate: Date) { + do { + let realm = try Realm() + try realm.write { + for blowby in self.blowbys { + blowby.date = combineDateWithCurrentTime(targetDate: newDate, targetTime: blowby.date) + _ = blowby.formattedDateTime(time: blowby.timeStamp,date: blowby.date) + } + } + } catch let error as NSError { + print("** REALM ERROR") + print(error) + } + } + + func addBlowby(blowby: BlowbyModel) -> BlowbyModel? { + blowby.shouldSync = true; + blowby.userId = self.userId; + do { + let realm = try Realm(); + try realm.write { + self.blowbys.append(blowby); + } + return blowby; + } catch let error as NSError { + print("** REALM ERROR"); + print(error); + return nil; + } + } // MARK: Setters func set(value: Any, for key: String) { + if BlowbyFields.contains(key){return} if self[key] == nil { print("\(key) is nil") return @@ -119,6 +153,7 @@ class ShiftModel: Object, BaseRealmObject { self.formattedDate = shiftStartDate.stringShort() } updateInspectionTimeStamps(newDate: shiftStartDate); + updateBlowbyTimeStamps(newDate: shiftStartDate) } } catch let error as NSError { print("** REALM ERROR") @@ -200,6 +235,20 @@ class ShiftModel: Object, BaseRealmObject { } return timeFormatter.string(from: timeInDate) } + + func deleteBlowby(blowbyToDelete: BlowbyModel) -> Void { + // Open a write transaction + do { + let realm = try Realm() + try realm.write { + // Delete the object from Realm + realm.delete(blowbyToDelete) + } + } catch { + print("Error deleting blowby: \(error)") + } + } + // MARK: To Dictionary func toDictionary() -> [String : Any] { let dateFormatter = DateFormatter() @@ -216,10 +265,10 @@ class ShiftModel: Object, BaseRealmObject { "endTime": endTimeFormatted, "station": station, "location": "NA", - "shiftStartComment": shitStartComments.count > 1 ? shitStartComments : "None", - "shiftEndComment": shitEndComments.count > 1 ? shitEndComments : "None", "motorizedBlowBys": motorizedBlowBys, "nonMotorizedBlowBys": nonMotorizedBlowBys, + "shiftStartComment": shitStartComments.count > 1 ? shitStartComments : "None", + "shiftEndComment": shitEndComments.count > 1 ? shitEndComments : "None", "boatsInspected": boatsInspected, "k9OnShift": k9OnShif ] @@ -233,4 +282,8 @@ class ShiftModel: Object, BaseRealmObject { func getShiftEndFields(editable: Bool) -> [InputItem] { return ShiftFormHelper.getShiftEndFields(for: self, editable: editable) } + + func getBlowbyFields(editable: Bool) -> [InputItem] { + return BlowByFormHelper.getBlowByFields(for: BlowbyModel()); + } } diff --git a/ipad/Services/ShiftService.swift b/ipad/Services/ShiftService.swift index d5762051..200716a8 100644 --- a/ipad/Services/ShiftService.swift +++ b/ipad/Services/ShiftService.swift @@ -21,6 +21,16 @@ class ShiftService { var promise: [String : Promise?] = [String : Promise]() + /// Submits a collection of shifts to the server. + /// + /// This function takes an array of `ShiftModel` instances and attempts to submit them to the server. + /// The submission process involves checking if the device is online and then iteratively submitting each shift. + /// Once a shift is successfully submitted, it updates the status of the shift and its associated inspections in local storage. + /// If the submission of a shift fails, the function stops the submission process and returns false. + /// + /// - Parameters: + /// - shifts: An array of `ShiftModel` instances representing the shifts to be submitted. + /// - then: A closure that gets called once all shifts have been submitted. It receives a boolean indicating whether the operation was successful. public func submit(shifts: [ShiftModel], then: @escaping (_ success: Bool) -> Void) { var remainingShifts = shifts if remainingShifts.count < 1 { @@ -31,7 +41,7 @@ class ShiftService { return then(false) } let shiftId = shift.localId - submit(shift: shift) { (submittedShift) in + submit(shift: shift) { (submittedShift) in if submittedShift, let refetchedShift = Storage.shared.shift(withLocalId: shiftId) { refetchedShift.set(shouldSync: false) refetchedShift.set(status: .Completed) @@ -46,17 +56,45 @@ class ShiftService { } } + /// Submits a single shift to the server. + /// + /// This function takes a `ShiftModel` instance and attempts to submit it to the server. + /// The submission process involves making a POST request to the server with the shift data. + /// Once the shift is successfully submitted, it updates the remote ID of the shift and its associated inspections and blowbys in local storage. + /// If the submission of the shift fails, the function returns false. + /// + /// - Parameters: + /// - shift: A `ShiftModel` instance representing the shift to be submitted. + /// - then: A closure that gets called once the shift has been submitted. It receives a boolean indicating whether the operation was successful. public func submit(shift: ShiftModel, then: @escaping (_ success: Bool) -> Void) { let shiftLocalId = shift.localId // Post call post(shift: shift) { (shiftId) in - // Fetch shift object - For realm thread issues - guard let rempteId = shiftId, let refetchedShift = Storage.shared.shift(withLocalId: shiftLocalId) else { + guard let remoteId = shiftId, let refetchedShift = Storage.shared.shift(withLocalId: shiftLocalId) else { return then(false) } - refetchedShift.set(remoteId: rempteId) - self.submit(inspections: Array(refetchedShift.inspections), shiftId: rempteId) { (success) in - return then(success) + refetchedShift.set(remoteId: remoteId) + + var inspectionSuccess = false + var blowBySuccess = false + + let group = DispatchGroup() + + group.enter() + self.submit(inspections: Array(refetchedShift.inspections), shiftId: remoteId) { (success) in + inspectionSuccess = success + group.leave() + } + + group.enter() + self.submit(theBlowBys: Array(refetchedShift.blowbys), shiftId: remoteId) { (success) in + blowBySuccess = success + group.leave() + } + + // Wait for both submissions to complete + group.notify(queue: .main) { + return then(inspectionSuccess && blowBySuccess) } } } @@ -93,6 +131,16 @@ class ShiftService { } } + /// Makes a POST request to the server with the shift data. + /// + /// This function takes a `ShiftModel` instance and attempts to send it to the server using a POST request. + /// The function checks if the device is online before making the request. + /// If the device is not online or if the request fails, the function returns nil. + /// Otherwise, it extracts the `observer_workflow_id` from the response and returns it. + /// + /// - Parameters: + /// - shift: A `ShiftModel` instance representing the shift data to be sent to the server. + /// - then: A closure that gets called once the request is completed. It receives the `observer_workflow_id` from the server response, or nil if the request failed or the device is not online. private func post(shift: ShiftModel, then: @escaping (_ id: Int?) -> Void) { if (!isOnline()) {return then(nil)} guard let endpoint = URL(string: APIURL.workflow) else { return then(nil)} @@ -109,6 +157,17 @@ class ShiftService { } } + /// Makes a POST request to the server with the inspection data. + /// + /// This function takes a `WatercraftInspectionModel` instance and attempts to send it to the server using a POST request. + /// The function checks if the device is online before making the request. + /// If the device is not online or if the request fails, the function returns nil. + /// Otherwise, it extracts the `watercraft_risk_assessment_id` from the response and returns it. + /// + /// - Parameters: + /// - inspection: A `WatercraftInspectionModel` instance representing the inspection data to be sent to the server. + /// - shift: The ID of the shift to which the inspection belongs. + /// - then: A closure that gets called once the request is completed. It receives the `watercraft_risk_assessment_id` from the server response, or nil if the request failed or the device is not online. private func post(inspection: WatercraftInspectionModel, shift id: Int,then: @escaping (_ id: Int?) -> Void) { if (!isOnline()) {return then(nil)} guard let endpoint = URL(string: APIURL.watercraftRiskAssessment) else { return then(nil)} @@ -125,7 +184,44 @@ class ShiftService { } } - // Submit inspection objects recursively + /// Makes a POST request to the server with the blow by data. + /// + /// This function takes a `BlowbyModel` instance and attempts to send it to the server using a POST request. + /// The function checks if the device is online before making the request. + /// If the device is not online or if the request fails, the function returns nil. + /// Otherwise, it extracts the `blow_by_id` from the response and returns it. + /// + /// - Parameters: + /// - blowBy: A `BlowbyModel` instance representing the blow by data to be sent to the server. + /// - shift: The ID of the shift to which the blow by belongs. + /// - then: A closure that gets called once the request is completed. It receives the `blow_by_id` from the server response, or nil if the request failed or the device is not online. + private func post(blowBy: BlowbyModel, shift id: Int,then: @escaping (_ id: Int?) -> Void) { + if (!isOnline()) {return then(nil)} + guard let endpoint = URL(string: APIURL.blowBys) else { return then(nil)} + APIRequest.request(type: .Post, endpoint: endpoint, params: blowBy.toDictionary(shift: id)) { (result) in + guard let responseJSON = result else { + Banner.show(message: "Didn't receive a valid API response when posting blow by") + return then(nil) + } + guard let dataDict = responseJSON["data"].dictionary, let id = dataDict["blow_by_id"]?.int else { + self.showError(in: responseJSON) + return then(nil) + } + return then(id) + } + } + + /// Recursively submits a collection of inspections to the server. + /// + /// This function takes an array of `WatercraftInspectionModel` instances and attempts to submit them to the server. + /// The submission process involves checking if the device is online and then iteratively submitting each inspection. + /// Once an inspection is successfully submitted, it updates the remote ID of the inspection in local storage. + /// If the submission of an inspection fails, the function stops the submission process and returns false. + /// + /// - Parameters: + /// - inspections: An array of `WatercraftInspectionModel` instances representing the inspections to be submitted. + /// - shiftId: The ID of the shift to which the inspections belong. + /// - then: A closure that gets called once all inspections have been submitted. It receives a boolean indicating whether the operation was successful. private func submit(inspections: [WatercraftInspectionModel], shiftId: Int,then: @escaping (_ success: Bool) -> Void) { var remainingInspections = inspections if remainingInspections.count < 1 { @@ -144,4 +240,38 @@ class ShiftService { self.submit(inspections: remainingInspections, shiftId: shiftId, then: then) } } + + /// Recursively submits a collection of blowbys to the server. + /// + /// This function takes an array of `BlowbyModel` instances and attempts to submit them to the server. + /// The submission process involves checking if the device is online and then iteratively submitting each blowby. + /// Once a blowby is successfully submitted, it updates the remote ID of the blowby in local storage. + /// If the submission of a blowby fails, the function stops the submission process and returns false. + /// + /// - Parameters: + /// - theBlowBys: An array of `BlowbyModel` instances representing the blowbys to be submitted. + /// - shiftId: The ID of the shift to which the blowbys belong. + /// - then: A closure that gets called once all blowbys have been submitted. It receives a boolean indicating whether the operation was successful. + private func submit(theBlowBys: [BlowbyModel], shiftId: Int, then: @escaping (_ success: Bool) -> Void) { + var remainingBlowBys = theBlowBys + if remainingBlowBys.count < 1 { + return then(true) + } + guard isOnline() else { + return then(false) + } + + guard let theBlowBy = remainingBlowBys.popLast() else { + return then(false) + } + let blowByLocalId = theBlowBy.localId + + post(blowBy: theBlowBy, shift: shiftId) { (blowById) in + guard let id = blowById, let refetchedBlowBy = Storage.shared.blowBy(withLocalId: blowByLocalId) else { + return then(false) + } + refetchedBlowBy.set(remoteId: id) + self.submit(theBlowBys: remainingBlowBys, shiftId: shiftId, then: then) + } + } } diff --git a/ipad/Utilities/Notifications/Notifications.swift b/ipad/Utilities/Notifications/Notifications.swift index 7c43b68f..aa65f05d 100644 --- a/ipad/Utilities/Notifications/Notifications.swift +++ b/ipad/Utilities/Notifications/Notifications.swift @@ -13,6 +13,7 @@ extension Notification.Name { static let ShouldResizeInputGroup = Notification.Name("ShouldResizeInputGroup") static let InputFieldShouldUpdate = Notification.Name("InputFieldShouldUpdate") static let TableButtonClicked = Notification.Name("tableButtonClicked") + static let BlowbyDeleteClicked = Notification.Name("BlowbyDeleteClicked") static let syncExecuted = Notification.Name("syncExecuted") static let shouldRefreshTable = Notification.Name("shouldRefreshTable") diff --git a/ipad/ViewControllers/Shift/Cells/Blowbys/ShiftBlowbysHeaderCollectionViewCell.swift b/ipad/ViewControllers/Shift/Cells/Blowbys/ShiftBlowbysHeaderCollectionViewCell.swift new file mode 100644 index 00000000..4804b2e9 --- /dev/null +++ b/ipad/ViewControllers/Shift/Cells/Blowbys/ShiftBlowbysHeaderCollectionViewCell.swift @@ -0,0 +1,54 @@ +// +// ShiftBlowBysHeaderCollectionViewCell.swift +// ipad +// +// Created by Amir Shayegh on 2019-11-21. +// Copyright © 2019 Amir Shayegh. All rights reserved. +// + +import UIKit + +class ShiftBlowBysHeaderCollectionViewCell: BaseShiftOverviewCollectionViewCell { + + // MARK: Outlets + @IBOutlet weak var addBlowByButton: UIButton! + @IBOutlet weak var divider: UIView! + @IBOutlet weak var titleLabel: UILabel! + + override func autofill() { + guard let model = self.model else {return} + if model.getStatus() != .Draft { + addBlowByButton.alpha = 0 + addBlowByButton.isEnabled = false + } + } + + // MARK: Outlet Actions + @IBAction func addBlowByClicked(_ sender: UIButton) { + if let callback = self.completion { + return callback() + } + } + + // MARK: Style + override func style() { + // Title + titleLabel.font = Fonts.getPrimaryBold(size: 24) + titleLabel.textColor = Colors.primary + // Button + styleFillButton(button: addBlowByButton) + // Divider + styleDivider(view: divider) + } + + private func colorFor(status: SyncableItemStatus) -> UIColor { + switch status { + case .PendingSync: + return Colors.Status.Yellow + case .Completed: + return Colors.Status.Green + case .Draft: + return Colors.Status.DarkGray + } + } +} diff --git a/ipad/ViewControllers/Shift/Cells/Blowbys/ShiftBlowbysHeaderCollectionViewCell.xib b/ipad/ViewControllers/Shift/Cells/Blowbys/ShiftBlowbysHeaderCollectionViewCell.xib new file mode 100644 index 00000000..8eca1da3 --- /dev/null +++ b/ipad/ViewControllers/Shift/Cells/Blowbys/ShiftBlowbysHeaderCollectionViewCell.xib @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ipad/ViewControllers/Shift/Cells/Header/BlowbyHeaderCollectionViewCell.xib b/ipad/ViewControllers/Shift/Cells/Header/BlowbyHeaderCollectionViewCell.xib new file mode 100644 index 00000000..c190b6e4 --- /dev/null +++ b/ipad/ViewControllers/Shift/Cells/Header/BlowbyHeaderCollectionViewCell.xib @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ipad/ViewControllers/Shift/Cells/Header/BlowbyOverviewHeaderCollectionViewCell.storyboard b/ipad/ViewControllers/Shift/Cells/Header/BlowbyOverviewHeaderCollectionViewCell.storyboard new file mode 100644 index 00000000..dd79351e --- /dev/null +++ b/ipad/ViewControllers/Shift/Cells/Header/BlowbyOverviewHeaderCollectionViewCell.storyboard @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ipad/ViewControllers/Shift/Cells/Header/ShifOverviewHeaderCollectionViewCell.xib b/ipad/ViewControllers/Shift/Cells/Header/ShifOverviewHeaderCollectionViewCell.xib index 222767a3..6d8d5f0c 100644 --- a/ipad/ViewControllers/Shift/Cells/Header/ShifOverviewHeaderCollectionViewCell.xib +++ b/ipad/ViewControllers/Shift/Cells/Header/ShifOverviewHeaderCollectionViewCell.xib @@ -1,23 +1,24 @@ - - + + - + + - + - - + - + - - + + + @@ -87,7 +89,6 @@ - @@ -98,7 +99,12 @@ - + + + + + + diff --git a/ipad/ViewControllers/Shift/Cells/Inspections/BlowbyTableCollectionViewCell.swift b/ipad/ViewControllers/Shift/Cells/Inspections/BlowbyTableCollectionViewCell.swift new file mode 100644 index 00000000..dc8c1b2f --- /dev/null +++ b/ipad/ViewControllers/Shift/Cells/Inspections/BlowbyTableCollectionViewCell.swift @@ -0,0 +1,120 @@ +// +// InspectionsTableCollectionViewCell.swift +// ipad +// +// Created by Amir Shayegh on 2019-11-21. +// Copyright © 2019 Amir Shayegh. All rights reserved. +// + +import UIKit + +class BlowbyTableCollectionViewCell: BaseShiftOverviewCollectionViewCell { + + // MARK: Constants + static let maxVisibleRows: CGFloat = 4 + + // MARK: Outlets + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var divider: UIView! + @IBOutlet weak var tableContainer: UIView! + @IBOutlet weak var tableHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var blowByButton: UIButton!; + // MARK: Class functions + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) + + // Specify you want _full width_ + let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0) + + // Calculate the size (height) using Auto Layout + let autoLayoutSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.defaultHigh) + let autoLayoutFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: autoLayoutSize) + + // Assign the new size to the layout attributes + autoLayoutAttributes.frame = autoLayoutFrame + return autoLayoutAttributes + } + + @IBAction func addBlowbyClicked(_ sender: UIButton) { + if let callback = self.completion { + return callback() + } + } + // MARK: Setup + override func autofill() { + guard let model = self.model else {return} + let table = Table() + + // Convert list to array + let blowbys: [BlowbyModel] = model.blowbys.map{ $0 } + + // Set table container height + tableHeightConstraint.constant = BlowbyTableCollectionViewCell.getTableHeight(for: model) + + // Create Column Config + var columns: [TableViewColumnConfig] = [] + columns.append(TableViewColumnConfig(key: "formattedReporttoRapp", header: "Reported to Rapp", type: .Normal)) + columns.append(TableViewColumnConfig(key: "watercraftComplexity", header: "Watercraft Complexity", type: .Normal)) + columns.append(TableViewColumnConfig(key: "timeStamp", header: "Blowby Time", type: .Normal)) + columns.append(TableViewColumnConfig(key: "", header: "Edit", type: .Button, buttonName: "Edit", showHeader: false)) + + // Disable adding blowbys if not completed and hide delete button + if model.getStatus() != .Draft { + columns.removeLast() + blowByButton.alpha = 0 + blowByButton.isEnabled = false + } + + let tableView = table.show(columns: columns, in: blowbys, container: tableContainer, emptyTitle: "It's looking a little empty around here.", emptyMessage: "You have not added any blowbys to this shift.") + + tableView.layoutIfNeeded() + self.layoutIfNeeded() + } + + static func getContentHeight(for model: ShiftModel) -> CGFloat { + let titleHeight: CGFloat = 25 + let dividerHeight: CGFloat = 2 + let paddings: CGFloat = 8 * 4 + let totalVertialTitleSize = titleHeight + dividerHeight + paddings + return getTableHeight(for: model) + totalVertialTitleSize + } + + static func getTableHeight(for model: ShiftModel) -> CGFloat { + if model.blowbys.isEmpty { + return 250 + } + // Convert list to array + + let blowbys: [BlowbyModel] = model.blowbys.map{ $0 } + let numberOfRows = blowbys.count + + let rowHeight = Table.rowHeight + let headerHeight = Table.headerLabelHeight + + if numberOfRows < 1 { + return headerHeight + } + + // TODO: FIX IT + let extraDEBUG: CGFloat = 20 + + if CGFloat(numberOfRows) > maxVisibleRows { + return ( maxVisibleRows * (rowHeight + extraDEBUG)) + headerHeight + extraDEBUG + } else { + return ( CGFloat(numberOfRows) * (rowHeight + extraDEBUG)) + headerHeight + extraDEBUG + } + } + + // MARK: Style + override func style() { + // Title + titleLabel.font = Fonts.getPrimaryBold(size: 28) + titleLabel.textColor = Colors.primary + // Button + styleFillButton(button: blowByButton) + + styleSectionTitle(label: titleLabel) + styleDivider(view: divider) + } + +} diff --git a/ipad/ViewControllers/Shift/Cells/Inspections/BlowbyTableCollectionViewCell.xib b/ipad/ViewControllers/Shift/Cells/Inspections/BlowbyTableCollectionViewCell.xib new file mode 100644 index 00000000..b1e371be --- /dev/null +++ b/ipad/ViewControllers/Shift/Cells/Inspections/BlowbyTableCollectionViewCell.xib @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ipad/ViewControllers/Shift/Cells/Inspections/InspectionsTableCollectionViewCell.xib b/ipad/ViewControllers/Shift/Cells/Inspections/InspectionsTableCollectionViewCell.xib index df127dd6..683f473e 100644 --- a/ipad/ViewControllers/Shift/Cells/Inspections/InspectionsTableCollectionViewCell.xib +++ b/ipad/ViewControllers/Shift/Cells/Inspections/InspectionsTableCollectionViewCell.xib @@ -1,9 +1,9 @@ - - + + - + @@ -19,9 +19,9 @@