Commit 9eb256b5 authored by Wei Han's avatar Wei Han

code update

parent 010b196a
......@@ -11,7 +11,7 @@
44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
AF643AF9AD2E6D7F8427E3F1 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF98AE45D9936432AFEC029 /* libPods-example.a */; };
F2581A09DD01A13570F68628 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D30A3A8F21F213E96F2C8F7 /* libPods-example.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
......@@ -19,11 +19,11 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = "<group>"; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = example/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
688F14F134731D6E3695DCD4 /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = "<group>"; };
6FF98AE45D9936432AFEC029 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
2D30A3A8F21F213E96F2C8F7 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
508BA7CC4A1A0B5A23D84649 /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = "<group>"; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = example/AppDelegate.swift; sourceTree = "<group>"; };
7C33988A67E3A60B09B56690 /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = "<group>"; };
E7FE0B6CF3283929F0ED2217 /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
......@@ -32,7 +32,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
AF643AF9AD2E6D7F8427E3F1 /* libPods-example.a in Frameworks */,
F2581A09DD01A13570F68628 /* libPods-example.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -55,7 +55,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
6FF98AE45D9936432AFEC029 /* libPods-example.a */,
2D30A3A8F21F213E96F2C8F7 /* libPods-example.a */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
E7FE0B6CF3283929F0ED2217 /* Pods-example.debug.xcconfig */,
688F14F134731D6E3695DCD4 /* Pods-example.release.xcconfig */,
7C33988A67E3A60B09B56690 /* Pods-example.debug.xcconfig */,
508BA7CC4A1A0B5A23D84649 /* Pods-example.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -105,13 +105,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = (
73D6515E0B05E56E1C38DCEE /* [CP] Check Pods Manifest.lock */,
D47CB7885794FE215FE8B238 /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
13DCD710B66171E18819239B /* [CP] Embed Pods Frameworks */,
E355AFBEE78FE218C8B2F995 /* [CP] Copy Pods Resources */,
ABA4014BAFCE28774B9D5225 /* [CP] Embed Pods Frameworks */,
4B920070F73F4233D4D79AC3 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -183,7 +183,24 @@
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
13DCD710B66171E18819239B /* [CP] Embed Pods Frameworks */ = {
4B920070F73F4233D4D79AC3 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
ABA4014BAFCE28774B9D5225 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -200,7 +217,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
73D6515E0B05E56E1C38DCEE /* [CP] Check Pods Manifest.lock */ = {
D47CB7885794FE215FE8B238 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -222,23 +239,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
E355AFBEE78FE218C8B2F995 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
......@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E7FE0B6CF3283929F0ED2217 /* Pods-example.debug.xcconfig */;
baseConfigurationReference = 7C33988A67E3A60B09B56690 /* Pods-example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......@@ -283,7 +283,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 688F14F134731D6E3695DCD4 /* Pods-example.release.xcconfig */;
baseConfigurationReference = 508BA7CC4A1A0B5A23D84649 /* Pods-example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......
......@@ -9,10 +9,11 @@
/* Begin PBXBuildFile section */
2F8D09092E9605E200C764FF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F8D09082E9605E200C764FF /* CoreGraphics.framework */; };
2F99420D2EA9C5F000622199 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F99420C2EA9C5F000622199 /* QuartzCore.framework */; };
2FAEF09A2E8B7BC80086EDA3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FAEF0992E8B7BC80086EDA3 /* UIKit.framework */; };
2FAEF09C2E8B7BD40086EDA3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FAEF09B2E8B7BD40086EDA3 /* Foundation.framework */; };
2FB2922A2EAB04F400D9F164 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FB292292EAB04F400D9F164 /* AVFoundation.framework */; };
2FB975E02ED7F9AA00E64A87 /* EventKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */; };
2FBC22B52EF93B8A00882B6F /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FBC22B42EF93B8A00882B6F /* UniformTypeIdentifiers.framework */; };
2FBC22B72EF93BB400882B6F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FBC22B62EF93BB400882B6F /* UIKit.framework */; };
2FD1557B2ECAC5CC00233981 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FD1557A2ECAC5CC00233981 /* WebKit.framework */; };
2FD9A1AE2EF0FA7100A7B53E /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FD9A0B72EF0E85B00A7B53E /* Network.framework */; };
/* End PBXBuildFile section */
......@@ -25,6 +26,8 @@
2FAEF09B2E8B7BD40086EDA3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
2FB292292EAB04F400D9F164 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.0.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EventKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/Library/Frameworks/EventKit.framework; sourceTree = DEVELOPER_DIR; };
2FBC22B42EF93B8A00882B6F /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
2FBC22B62EF93BB400882B6F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
2FD027B02EEFD25100CF5C33 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/Library/Frameworks/Network.framework; sourceTree = DEVELOPER_DIR; };
2FD1557A2ECAC5CC00233981 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/iOSSupport/System/Library/PrivateFrameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
2FD9A0B72EF0E85B00A7B53E /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; };
......@@ -34,7 +37,7 @@
2FD9A0B92EF0F7FD00A7B53E /* Exceptions for "QmsPluginFramework" folder in "QmsPluginFramework" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
FSCalendar/Info.plist,
Utilities/FSCalendar/Info.plist,
);
publicHeaders = (
QmsPlugin.h,
......@@ -60,6 +63,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2FBC22B72EF93BB400882B6F /* UIKit.framework in Frameworks */,
2FBC22B52EF93B8A00882B6F /* UniformTypeIdentifiers.framework in Frameworks */,
2FD9A1AE2EF0FA7100A7B53E /* Network.framework in Frameworks */,
2FB975E02ED7F9AA00E64A87 /* EventKit.framework in Frameworks */,
2FD1557B2ECAC5CC00233981 /* WebKit.framework in Frameworks */,
......@@ -67,7 +72,6 @@
2F99420D2EA9C5F000622199 /* QuartzCore.framework in Frameworks */,
2F8D09092E9605E200C764FF /* CoreGraphics.framework in Frameworks */,
2FAEF09C2E8B7BD40086EDA3 /* Foundation.framework in Frameworks */,
2FAEF09A2E8B7BC80086EDA3 /* UIKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -94,6 +98,8 @@
2FAEF0982E8B7BC80086EDA3 /* Frameworks */ = {
isa = PBXGroup;
children = (
2FBC22B62EF93BB400882B6F /* UIKit.framework */,
2FBC22B42EF93B8A00882B6F /* UniformTypeIdentifiers.framework */,
2FD027B02EEFD25100CF5C33 /* Network.framework */,
2FD9A0B72EF0E85B00A7B53E /* Network.framework */,
2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */,
......
......@@ -5,6 +5,7 @@
#import <UIKit/UIKit.h>
#import "AccessAppointmentViewController.h"
#import "AccessTransactionViewController.h"
#import "PullToRefresh.h"
// Custom Card
@interface SubmittedItemCell : UITableViewCell
......@@ -357,6 +358,11 @@
[self.tableView registerClass:[SubmittedItemCell class] forCellReuseIdentifier:@"SubmittedItemCell"];
[self.tableView registerClass:[TransactionItemCell class] forCellReuseIdentifier:@"TransactionItemCell"];
__weak typeof(self) weakSelf = self;
[self.tableView addPullToRefresh:^(UIRefreshControl *refresh) {
[weakSelf fetchAccessItem];
}];
[self.view addSubview:self.tableView];
......@@ -391,6 +397,7 @@
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.tableView endPullToRefresh];
});
}];
}
......
// AddInfoViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AddInfoViewController : UIViewController
@property (nonatomic, strong) NSString *issueID;
@property (nonatomic, strong) NSString *planID;
@property (nonatomic, strong) NSString *status;
@property (nonatomic, strong) NSString *unitName;
@property (nonatomic, strong) NSString *reference;
@end
NS_ASSUME_NONNULL_END
......@@ -2,6 +2,8 @@
#import "APIClient.h"
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "OfflineSyncManager.h"
#import "NetworkManager.h"
@interface AddIssueDetailsViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, strong) UIView *headerView;
......@@ -20,6 +22,7 @@
@property (nonatomic, assign) BOOL isEditMode;
@property (nonatomic, strong) NSMutableArray *existingImages;
@property (nonatomic, strong) NSMutableArray *addedImages;
@end
@implementation AddIssueDetailsViewController
......@@ -911,6 +914,20 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
};
NSLog(@"📦 Sending UpdateIssue with data: %@", updateData);
if (![NetworkManager.sharedManager isConnected]) {
[[OfflineSyncManager shared] queueUpdateIssue:updateData
images:self.addedImages];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
[APIClient submitUpdateIssue:updateData
images:self.addedImages
......@@ -970,7 +987,23 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
@"comment": self.commentField.text ?: @"",
@"os": @"AND"
};
NSLog(@"NetworkManager.sharedManager isConnected = %@",
[NetworkManager.sharedManager isConnected] ? @"YES" : @"NO");
if (![NetworkManager.sharedManager isConnected]) {
[[OfflineSyncManager shared] queueAddIssue:issueData
images:self.uploadedImages];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
[APIClient submitAddIssue:issueData
images:self.uploadedImages
completion:^(NSDictionary *response, NSError *error) {
......@@ -1014,7 +1047,6 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
}
}
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
message:message
......
......@@ -4,6 +4,7 @@
#import "APIClient.h"
#import "CalendarView.h"
#import "NSDate+CalendarHelpers.h"
#import "AddInfoViewController.h"
@interface HistoryViewController () <UITableViewDelegate, UITableViewDataSource>
......@@ -78,7 +79,12 @@
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.titleLabel];
// need to make addInfo eventually
self.addInfoButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.addInfoButton setImage:[UIImage systemImageNamed:@"document.badge.plus"] forState:UIControlStateNormal];
self.addInfoButton.tintColor = UIColor.whiteColor;
self.addInfoButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.addInfoButton addTarget:self action:@selector(addInfoTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:self.addInfoButton];
[NSLayoutConstraint activateConstraints:@[
[self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
......@@ -88,6 +94,9 @@
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:12],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.addInfoButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-12],
[self.addInfoButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
......@@ -434,6 +443,14 @@
- (void)addInfoTapped {
NSLog(@"➕ Add Info tapped");
AddInfoViewController *vc = [[AddInfoViewController alloc] init];
vc.issueID = self.issueID;
vc.planID = self.planID;
vc.status = self.status;
vc.unitName = self.unitName;
vc.reference = self.reference;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
}
#pragma mark - helper
......
......@@ -170,7 +170,7 @@
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Issues";
self.titleLabel.text = @"Issue(s)";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:20];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
......
......@@ -44,10 +44,17 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)fetchHistory:(NSString *)issueID
completion:(void (^)(NSDictionary *data, NSError *error))completion;
+(void)fetchAddInfo:(NSString *)issueID
+(void)submitAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
completion:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)uploadAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL *)mediaURL
mediaType:(NSString *)mediaType
completion:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)fetchIssues:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)fetchVoidIssue:(id)issueID
......
......@@ -390,10 +390,6 @@
appendFormField(@"data[comment]", updateData[@"comment"]);
appendFormField(@"data[os]", updateData[@"os"]);
// 🔹 Add `information` field (as JSON string)
// NSString *deviceInfo = @"{\"OS\":\"AND\",\"IMEI\":\"AND:2a2f13ff17b1cbb5\",\"OS_VERSION\":\"35\",\"MODEL\":\"2306EPN60G\",\"APP_VERSION\":\"1.22.26\"}";
// appendFormField(@"information", deviceInfo);
// 🔹 Append image files
for (int i = 0; i < images.count; i++) {
id maybeImage = images[i];
......@@ -454,7 +450,7 @@
}];
[task resume];
}
+ (void)fetchAppointment:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/getSlot?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
......@@ -984,7 +980,7 @@
[task resume];
}
+(void)fetchAddInfo:(NSString *)issueID
+(void)submitAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
......@@ -1061,6 +1057,124 @@
[task resume];
}
+ (void)uploadAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL *)mediaURL
mediaType:(NSString *)mediaType
completion:(void (^)(NSDictionary *data, NSError *error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/addInfo?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"; NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
// 🧱 Multipart body
NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSMutableData *body = [NSMutableData data];
// Helper for adding form fields
void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"%@\r\n", value]
dataUsingEncoding:NSUTF8StringEncoding]];
};
// 🔹 Append OS info
appendFormField(@"data[issue_id]", issueID ?: @"");
appendFormField(@"data[remarks]", remarks ?: @"");
appendFormField(@"data[os]", @"AND");
// 🔹 Append images
if (images.count > 0) {
for (int i = 0; i < images.count; i++) {
id maybeImage = images[i];
if (![maybeImage isKindOfClass:[UIImage class]]) {
NSLog(@"⚠️ Skipping non-UIImage at index %d: %@", i, maybeImage);
continue; // skip invalid entries
}
UIImage *image = (UIImage *)maybeImage;
NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
if (!imageData) continue;
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:
@"Content-Disposition: form-data; name=\"data[image][%d]\"; filename=\"image%d.jpg\"\r\n", i, i]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:imageData];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
}
// 🔹 Append media file if provided
if (mediaURL) {
NSData *fileData = [NSData dataWithContentsOfURL:mediaURL];
if (fileData) {
NSString *ext = mediaURL.pathExtension.lowercaseString;
NSString *mimeType = @"application/octet-stream";
if ([mediaType isEqualToString:@"Video"]) {
mimeType = [NSString stringWithFormat:@"video/%@", ext];
}
NSInteger index = images.count;
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:
@"Content-Disposition: form-data; name=\"data[image][%ld]\"; filename=\"addinfo.%@\"\r\n",
(long)index, ext]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:
@"Content-Type: %@\r\n\r\n", mimeType]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:fileData];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
}
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = body;
// Debug
NSLog(@"📦 [AddIssue] Uploading %lu image(s)", (unsigned long)images.count);
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ [AddIssue] Network error: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(nil, error); });
return;
}
NSError *jsonErr;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
if (jsonErr) {
NSString *raw = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"⚠️ [AddIssue] JSON parse error: %@\nRaw response: %@", jsonErr, raw);
dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(nil, jsonErr); });
return;
}
NSLog(@"✅ [AddIssue] Response: %@", json);
dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(json, nil); });
}];
[task resume];
}
+ (void)fetchIssues:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/getIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
......
......@@ -45,17 +45,25 @@
nw_path_monitor_set_queue(self.monitor, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
nw_path_monitor_set_update_handler(self.monitor, ^(nw_path_t path) {
self.connected = nw_path_get_status(path) == nw_path_status_satisfied;
NSLog(@"[NetworkManager] Internet connectivity: %@", self.connected ? @"YES" : @"NO");
// Post notification for all listeners
BOOL hasInternet = (nw_path_get_status(path) == nw_path_status_satisfied);
// BOOL isWiFi = nw_path_uses_interface_type(path, nw_interface_type_wifi);
// BOOL isCellular = nw_path_uses_interface_type(path, nw_interface_type_cellular);
// BOOL isEthernet = nw_path_uses_interface_type(path, nw_interface_type_wired);
self.connected = hasInternet;
// && (isWiFi || isCellular || isEthernet);
// note that simulator might not report correct status
NSLog(@"[NetworkManager] Connected = %@", self.connected ? @"YES" : @"NO");
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkStatusChanged"
object:nil
userInfo:@{@"connected": @(self.connected)}];
});
});
nw_path_monitor_start(self.monitor);
}
......
// OfflineSyncManager.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface OfflineSyncManager : NSObject
+ (instancetype)shared;
// Queue actions
- (void)queueAddIssue:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images;
- (void)queueUpdateIssue:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images;
- (void)queueAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL * _Nullable)mediaURL
mediaType:(NSString * _Nullable)mediaType;
// Sync
- (void)processQueueIfOnline;
// Helpers
- (BOOL)hasPendingItems;
@end
NS_ASSUME_NONNULL_END
// OfflineSyncManager.mm
#import "OfflineSyncManager.h"
#import "LocalStorage.h"
#import "NetworkManager.h"
#import "APIClient.h"
static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
@implementation OfflineSyncManager
#pragma mark - Singleton
+ (instancetype)shared {
static OfflineSyncManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[OfflineSyncManager alloc] init];
[instance setupNetworkListener];
});
return instance;
}
#pragma mark - Setup
- (void)setupNetworkListener {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(networkStatusChanged:)
name:@"NetworkStatusChanged"
object:nil];
}
- (void)networkStatusChanged:(NSNotification *)note {
BOOL connected = [note.userInfo[@"connected"] boolValue];
if (connected) {
[self processQueueIfOnline];
}
}
#pragma mark - Queue Helpers (CRITICAL)
- (NSMutableArray *)safeLoadQueue {
id stored = [LocalStorage loadArrayForKey:kOfflineQueueKey];
if (![stored isKindOfClass:[NSArray class]]) {
NSLog(@"🧹 [OfflineSync] Queue corrupted — resetting");
[LocalStorage removeObjectForKey:kOfflineQueueKey];
return [NSMutableArray array];
}
NSMutableArray *validItems = [NSMutableArray array];
for (id obj in stored) {
if ([obj isKindOfClass:[NSDictionary class]]) {
[validItems addObject:obj];
}
}
return validItems;
}
- (void)saveQueue:(NSArray *)queue {
[LocalStorage saveArray:queue forKey:kOfflineQueueKey];
}
#pragma mark - Public Queue APIs
- (void)queueAddIssue:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images {
[self enqueueAction:@"ADD_ISSUE"
payload:payload
images:images];
}
- (void)queueUpdateIssue:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images {
[self enqueueAction:@"UPDATE_ISSUE"
payload:payload
images:images];
}
- (void)queueAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL * _Nullable)mediaURL
mediaType:(NSString * _Nullable)mediaType {
NSMutableArray *queue = [self safeLoadQueue];
NSArray *imagePaths = [self saveImagesToDisk:images];
NSString *mediaPath = [self saveMediaToDisk:mediaURL];
NSMutableDictionary *item = [NSMutableDictionary dictionary];
item[@"id"] = NSUUID.UUID.UUIDString;
item[@"type"] = @"ADD_INFO";
item[@"issue_id"] = issueID ?: @"";
item[@"remarks"] = remarks ?: @"";
item[@"imagePaths"] = imagePaths ?: @[];
item[@"timestamp"] = @((NSInteger)[NSDate date].timeIntervalSince1970);
if (mediaPath) item[@"mediaPath"] = mediaPath;
if (mediaType) item[@"mediaType"] = mediaType;
[queue addObject:item];
[self saveQueue:queue];
NSLog(@"📥 [OfflineSync] Queued ADD_INFO (%lu)", (unsigned long)queue.count);
}
#pragma mark - Core Queue Add
- (void)enqueueAction:(NSString *)type
payload:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images {
NSMutableArray *queue = [self safeLoadQueue];
NSArray *imagePaths = [self saveImagesToDisk:images];
NSDictionary *item = @{
@"id": NSUUID.UUID.UUIDString,
@"type": type,
@"payload": payload ?: @{},
@"imagePaths": imagePaths ?: @[],
@"timestamp": @((NSInteger)[NSDate date].timeIntervalSince1970)
};
[queue addObject:item];
[self saveQueue:queue];
NSLog(@"📥 [OfflineSync] Queued %@ (%lu)", type, (unsigned long)queue.count);
}
#pragma mark - Processing Queue
- (void)processQueueIfOnline {
if (![NetworkManager.sharedManager isConnected]) {
return;
}
NSMutableArray *queue = [self safeLoadQueue];
if (queue.count == 0) return;
NSDictionary *item = queue.firstObject;
if (![item isKindOfClass:[NSDictionary class]]) {
[queue removeObjectAtIndex:0];
[self saveQueue:queue];
[self processQueueIfOnline];
return;
}
[self processItem:item completion:^(BOOL success) {
if (success) {
[queue removeObjectAtIndex:0];
[self saveQueue:queue];
[self processQueueIfOnline];
}
}];
}
- (void)processItem:(NSDictionary *)item
completion:(void (^)(BOOL success))completion {
NSString *type = item[@"type"] ?: @"";
if ([type isEqualToString:@"ADD_INFO"]) {
NSArray *images = [self loadImagesFromPaths:item[@"imagePaths"]];
NSURL *mediaURL = item[@"mediaPath"] ? [NSURL fileURLWithPath:item[@"mediaPath"]] : nil;
BOOL hasImages = images.count > 0;
BOOL hasMedia = mediaURL != nil;
if (!hasImages && !hasMedia) {
[APIClient submitAddInfo:item[@"issue_id"]
remarks:item[@"remarks"]
completion:^(NSDictionary *r, NSError *e) {
completion(e == nil);
}];
} else {
[APIClient uploadAddInfo:item[@"issue_id"]
remarks:item[@"remarks"]
images:images
mediaURL:mediaURL
mediaType:item[@"mediaType"]
completion:^(NSDictionary *r, NSError *e) {
completion(e == nil);
}];
}
return;
}
NSLog(@"⚠️ Unknown offline type: %@", type);
completion(NO);
}
#pragma mark - Disk Handling
- (NSArray *)saveImagesToDisk:(NSArray<UIImage *> *)images {
NSMutableArray *paths = [NSMutableArray array];
if (![images isKindOfClass:[NSArray class]]) return paths;
NSString *dir = [self offlineDirectory];
for (id obj in images) {
if (![obj isKindOfClass:[UIImage class]]) continue;
NSString *path = [dir stringByAppendingPathComponent:
[NSString stringWithFormat:@"%@.jpg", NSUUID.UUID.UUIDString]];
NSData *data = UIImageJPEGRepresentation(obj, 0.8);
if (data && [data writeToFile:path atomically:YES]) {
[paths addObject:path];
}
}
return paths;
}
- (NSString *)saveMediaToDisk:(NSURL *)url {
if (!url) return nil;
NSString *dir = [self offlineDirectory];
NSString *path = [dir stringByAppendingPathComponent:
[NSString stringWithFormat:@"%@.%@",
NSUUID.UUID.UUIDString, url.pathExtension]];
NSData *data = [NSData dataWithContentsOfURL:url];
if ([data writeToFile:path atomically:YES]) {
return path;
}
return nil;
}
- (NSArray *)loadImagesFromPaths:(NSArray *)paths {
NSMutableArray *images = [NSMutableArray array];
for (NSString *path in paths) {
UIImage *img = [UIImage imageWithContentsOfFile:path];
if (img) [images addObject:img];
}
return images;
}
- (NSString *)offlineDirectory {
NSString *dir = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
dir = [dir stringByAppendingPathComponent:@"offline_files"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dir]) {
[[NSFileManager defaultManager] createDirectoryAtPath:dir
withIntermediateDirectories:YES
attributes:nil
error:nil];
}
return dir;
}
#pragma mark - Status
- (BOOL)hasPendingItems {
return ([self safeLoadQueue].count > 0);
}
@end
// PullToRefresh.h
#import <UIKit/UIKit.h>
typedef void (^PullToRefreshAction)(UIRefreshControl *refreshControl);
@interface UIScrollView (PullToRefresh)
- (UIRefreshControl *)addPullToRefresh:(PullToRefreshAction)action;
- (void)endPullToRefresh;
@end
// PullToRefresh.mm
#import "PullToRefresh.h"
#import <objc/runtime.h>
static char kRefreshControlKey;
static char kRefreshActionKey;
@implementation UIScrollView (PullToRefresh)
- (UIRefreshControl *)addPullToRefresh:(PullToRefreshAction)action {
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
objc_setAssociatedObject(self,
&kRefreshActionKey,
action,
OBJC_ASSOCIATION_COPY_NONATOMIC);
[refreshControl addTarget:self
action:@selector(handlePullToRefresh:)
forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
objc_setAssociatedObject(self,
&kRefreshControlKey,
refreshControl,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return refreshControl;
}
- (void)handlePullToRefresh:(UIRefreshControl *)sender {
PullToRefreshAction action =
objc_getAssociatedObject(self, &kRefreshActionKey);
if (action) {
action(sender);
}
}
- (void)endPullToRefresh {
UIRefreshControl *refreshControl =
objc_getAssociatedObject(self, &kRefreshControlKey);
if (refreshControl.isRefreshing) {
[refreshControl endRefreshing];
}
}
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment