Commit 8eaaf67d authored by Wei Han's avatar Wei Han

code update

parent 9eb256b5
......@@ -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 */; };
F2581A09DD01A13570F68628 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D30A3A8F21F213E96F2C8F7 /* libPods-example.a */; };
FB2FBB7BA35D68426456E2DE /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A86FB22DA3710B6C48332E2 /* 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>"; };
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>"; };
502E3324911D1E5D0C0EFA69 /* 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>"; };
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>"; };
8A86FB22DA3710B6C48332E2 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
90821031277E35B5DA493B2D /* 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>"; };
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 = (
F2581A09DD01A13570F68628 /* libPods-example.a in Frameworks */,
FB2FBB7BA35D68426456E2DE /* libPods-example.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -55,7 +55,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
2D30A3A8F21F213E96F2C8F7 /* libPods-example.a */,
8A86FB22DA3710B6C48332E2 /* libPods-example.a */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
7C33988A67E3A60B09B56690 /* Pods-example.debug.xcconfig */,
508BA7CC4A1A0B5A23D84649 /* Pods-example.release.xcconfig */,
502E3324911D1E5D0C0EFA69 /* Pods-example.debug.xcconfig */,
90821031277E35B5DA493B2D /* Pods-example.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -105,13 +105,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = (
D47CB7885794FE215FE8B238 /* [CP] Check Pods Manifest.lock */,
9AD4CB19B4D3412BF066BD3E /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
ABA4014BAFCE28774B9D5225 /* [CP] Embed Pods Frameworks */,
4B920070F73F4233D4D79AC3 /* [CP] Copy Pods Resources */,
9B7F90516C6E40D72BFE9346 /* [CP] Embed Pods Frameworks */,
DD2A8D0D8F75B0C93112FC8D /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -183,24 +183,29 @@
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";
};
4B920070F73F4233D4D79AC3 /* [CP] Copy Pods Resources */ = {
9AD4CB19B4D3412BF066BD3E /* [CP] Check Pods Manifest.lock */ = {
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";
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
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;
};
ABA4014BAFCE28774B9D5225 /* [CP] Embed Pods Frameworks */ = {
9B7F90516C6E40D72BFE9346 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -217,26 +222,21 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
D47CB7885794FE215FE8B238 /* [CP] Check Pods Manifest.lock */ = {
DD2A8D0D8F75B0C93112FC8D /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
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";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
......@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7C33988A67E3A60B09B56690 /* Pods-example.debug.xcconfig */;
baseConfigurationReference = 502E3324911D1E5D0C0EFA69 /* Pods-example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......@@ -283,7 +283,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 508BA7CC4A1A0B5A23D84649 /* Pods-example.release.xcconfig */;
baseConfigurationReference = 90821031277E35B5DA493B2D /* Pods-example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......
......@@ -8,32 +8,32 @@
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
......
......@@ -11,6 +11,7 @@
#import "AppointmentViewMoreViewController.h"
#import "GeneralInfoViewController.h"
#import "AccessItemViewController.h"
#import "OfflineSyncManager.h"
@interface DashboardViewController ()
......@@ -78,6 +79,9 @@
[self checkAndRefreshAPIs];
NSLog(@"in viewDidLoad ClientID: %@, ClientCode: %@, user_token: %@", self.ClientID, self.ClientCode, self.user_token);
NSLog(@"dashboard.drawerController = %@", self.drawerController);
// [[OfflineSyncManager shared] clearOfflineQueue];
}
- (void)viewWillAppear:(BOOL)animated {
......@@ -1132,6 +1136,8 @@
IssueDetailViewController *nextVC = [[IssueDetailViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
nextVC.issueData = issueData;
nextVC.projectCode = self.projectCode;
nextVC.projectName = self.projectName;
[self presentViewController:nextVC animated:YES completion:nil];
} else {
NSLog(@"⚠️ No valid issue data found for tapped card: %@", issueData);
......@@ -1182,6 +1188,8 @@
-(void)issueHeaderTapped:(UITapGestureRecognizer *)gesture {
IssuesViewController *nextVC = [[IssuesViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
nextVC.projectCode = self.projectCode;
nextVC.projectName = self.projectName;
[self presentViewController:nextVC animated:YES completion:nil];
}
......
......@@ -10,6 +10,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong) NSString *status;
@property (nonatomic, strong) NSString *unitName;
@property (nonatomic, strong) NSString *reference;
@property (nonatomic, strong) NSString *userName;
@property (nonatomic, strong) NSString *projectName;
@property (nonatomic, strong) NSString *projectCode;
@end
......
......@@ -565,14 +565,32 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
}
if (![NetworkManager.sharedManager isConnected]) {
NSDictionary *displayData = @{
@"projectName": self.projectName ?: @"Unknown Project",
@"unitName": self.projectCode ?: @"-",
@"createdBy": @"Athirah Zaidi",
@"createdAt": [self todayDateString],
@"issueDisplay": @"Add Info",
};
NSLog(@"add info displayData %@", displayData);
[[OfflineSyncManager shared] queueAddInfo:self.issueID
remarks:self.commentTextView.text
displayInfo:displayData
images:images
mediaURL:self.selectedMediaURL
mediaType:mediaType];
[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];
return;
} else {
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
}
if (!hasImages && !hasMedia) {
......@@ -669,4 +687,11 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
return valid;
}
- (NSString *)todayDateString {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"yyyy-MM-dd";
return [formatter stringFromDate:[NSDate date]];
}
@end
......@@ -916,7 +916,17 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
NSLog(@"📦 Sending UpdateIssue with data: %@", updateData);
if (![NetworkManager.sharedManager isConnected]) {
NSDictionary *displayData = @{
@"projectName": self.projectName ?: @"Unknown Project",
@"unitName": self.projectCode ?: @"-",
@"createdBy": @"Athirah Zaidi",
@"createdAt": [self todayDateString],
@"issueDisplay": @"Edit Issue",
};
NSLog(@"edit issues displayData %@", displayData);
[[OfflineSyncManager shared] queueUpdateIssue:updateData
display:displayData
images:self.addedImages];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
......@@ -924,8 +934,10 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
return;
} else {
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
}
......@@ -988,10 +1000,18 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
@"os": @"AND"
};
NSLog(@"NetworkManager.sharedManager isConnected = %@",
[NetworkManager.sharedManager isConnected] ? @"YES" : @"NO");
if (![NetworkManager.sharedManager isConnected]) {
NSDictionary *displayData = @{
@"projectName": self.projectName ?: @"Unknown Project",
@"unitName": self.projectCode ?: @"-",
@"createdBy": @"Athirah Zaidi",
@"createdAt": [self todayDateString],
@"issueDisplay": @"Add Issue",
};
NSLog(@"add issue displayData %@", displayData);
[[OfflineSyncManager shared] queueAddIssue:issueData
display:displayData
images:self.uploadedImages];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
......@@ -999,8 +1019,10 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
return;
} else {
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
}
......@@ -1095,4 +1117,11 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
self.scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
}
- (NSString *)todayDateString {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"yyyy-MM-dd";
return [formatter stringFromDate:[NSDate date]];
}
@end
......@@ -770,7 +770,7 @@
nextVC.locationData = mainData;
nextVC.defectMatrix = defectMatrix;
nextVC.projectCode = self.projectCode ?: @"";
nextVC.projectName = self.unitName;
nextVC.projectName = self.projectName;
nextVC.planX = self.tappedPlanX;
nextVC.planY = self.tappedPlanY;
......
......@@ -11,6 +11,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong) NSString *status;
@property (nonatomic, strong) NSString *unitName;
@property (nonatomic, strong) NSString *reference;
@property (nonatomic, strong) NSString *projectName;
@property (nonatomic, strong) NSString *projectCode;
@end
......
......@@ -16,6 +16,7 @@
@property (nonatomic, strong) NSArray *historyItems;
@property (nonatomic, strong) NSArray *sortedDates;
@property (nonatomic, strong) NSDictionary *groupedHistory;
@property (nonatomic, strong) NSString *userName;
@end
......@@ -48,9 +49,9 @@
[self setupTableView];
[self handleIssues];
NSLog(@"📦 Data received by History:\n issueID: %@\n planID: %@\n status: %@\n unitName: %@\n reference: %@",
self.issueID,
self.planID,
NSLog(@"📦 Data received by History:\n projectName: %@\n projectCode: %@\n status: %@\n unitName: %@\n reference: %@",
self.projectName,
self.projectCode,
self.status,
self.unitName,
self.reference);
......@@ -346,7 +347,7 @@
UIScrollView *thumbScroll = [cell viewWithTag:105];
NSDictionary *user = item[@"user"];
NSString *userName = [self safeString:user[@"name"]];
self.userName = [self safeString:user[@"name"]];
NSString *remark = [self safeString:item[@"remarks"]];
NSString *status = [self safeString:item[@"status_external"]];
NSString *colorHex = [self safeString:item[@"status_ex_color"]];
......@@ -362,7 +363,7 @@
}
timeLabel.text = timePart.length > 0 ? timePart : @"--";
userLabel.text = userName;
userLabel.text = self.userName;
remarkLabel.text = remark;
dateLabel.text = datePost;
statusLabel.text = status;
......@@ -449,6 +450,9 @@
vc.status = self.status;
vc.unitName = self.unitName;
vc.reference = self.reference;
vc.userName = self.userName;
vc.projectCode = self.projectCode;
vc.projectName = self.projectName;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
}
......
......@@ -7,6 +7,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface IssueDetailViewController : UIViewController
@property (nonatomic, strong) NSDictionary *issueData;
@property (nonatomic, strong) NSString *projectName;
@property (nonatomic, strong) NSString *projectCode;
@end
......
......@@ -25,19 +25,8 @@
self.view.backgroundColor = [UIColor systemGray6Color];
self.title = @"Issue Details";
NSLog(@"📦 Received Issue Data: %@", self.issueData);
NSString *text = [NSString stringWithFormat:@"%@", self.issueData];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"issueData.txt"];
// Convert issueData to a readable string
NSString *plainText = [NSString stringWithFormat:@"%@", self.issueData];
// Write as plain UTF-8 text
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
// Log the path so you can find it
NSLog(@"📁 issueData saved as plain text to: %@", filePath);
NSLog(@"📦 navigated to issueDetailViewController with:\n self.projectName=%@\n self.projectCode=%@",
self.projectCode, self.projectName);
[self setupHeader];
[self setupScrollView];
......@@ -559,10 +548,12 @@
nextVC.status = self.issueData[@"status_external"];
nextVC.unitName = self.issueData[@"plan_unit"];
nextVC.reference = self.issueData[@"reference"] ?: self.issueData[@"issue_reference"];
nextVC.projectCode = self.projectCode;
nextVC.projectName = self.projectName;
NSLog(@"📦 Data being sent to History:\n issueID: %@\n planID: %@\n status: %@\n unitName: %@\n reference: %@",
nextVC.issueID,
nextVC.planID,
nextVC.projectCode,
nextVC.projectName,
nextVC.status,
nextVC.unitName,
nextVC.reference);
......@@ -623,9 +614,11 @@
planVC.locationId = strLocationID;
planVC.issueContent = issueContent;
planVC.issueFullData = data;
planVC.projectCode = self.projectCode;
planVC.projectName = self.projectName;
NSLog(@"🧭 Navigating to PlanScreen with:\n type=%@\n unitID=%@\n unitName=%@\n location= %@ & %@\n issue_id=%@\n",
planVC.type, planVC.unitID, planVC.unitName, planVC.locationName, planVC.locationId, planVC.issueContent[@"issue_id"]);
NSLog(@"🧭 Navigating to PlanScreen with:\n projectCode=%@\n projectName=%@\n unitName=%@\n location= %@ & %@\n issue_id=%@\n",
planVC.projectCode, planVC.projectName, planVC.unitName, planVC.locationName, planVC.locationId, planVC.issueContent[@"issue_id"]);
[self presentViewController:planVC animated:YES completion:nil];
}
......
......@@ -6,6 +6,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface IssuesViewController : UIViewController
@property (nonatomic, strong) NSString *projectCode;
@property (nonatomic, strong) NSString *projectName;
@end
NS_ASSUME_NONNULL_END
......@@ -720,7 +720,8 @@
IssueDetailViewController *detailVC = [[IssueDetailViewController alloc] init];
detailVC.modalPresentationStyle = UIModalPresentationFullScreen;
detailVC.issueData = issue; // Pass the selected data
detailVC.projectCode = self.projectCode;
detailVC.projectName = self.projectName;
NSLog(@"🧭 Navigating to IssueDetailsViewController with issue: %@", issue);
[self presentViewController:detailVC animated:YES completion:nil];
......
......@@ -2,6 +2,7 @@
#import "SyncPendingViewController.h"
#import "PendingDetailsViewController.h"
#import "OfflineSyncManager.h"
typedef NS_ENUM(NSInteger, SyncPendingViewMode) {
SyncPendingViewModeList,
......@@ -154,9 +155,20 @@ typedef NS_ENUM(NSInteger, SyncPendingViewMode) {
self.viewMode = SyncPendingViewModeList;
self.view.backgroundColor = [UIColor systemGroupedBackgroundColor];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(loadPendingData)
name:@"OfflineSyncUpdated"
object:nil];
[self setupHeader];
[self setupTableView];
[self setupDummyData];
[self loadPendingData];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Header
......@@ -240,7 +252,23 @@ typedef NS_ENUM(NSInteger, SyncPendingViewMode) {
]];
}
//Dummy Data
#pragma mark - Data
- (void)loadPendingData {
NSArray *pending =
[[OfflineSyncManager shared] pendingItemsForUI];
NSLog(@"🖥 [SyncPendingVC] Pending sections: %@", pending);
if (pending.count > 0) {
self.sections = pending;
} else {
[self setupDummyData];
}
[self reloadUI];
}
- (void)setupDummyData {
self.sections = @[
@{
......@@ -304,10 +332,8 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *item =
self.sections[indexPath.section][@"items"][indexPath.row];
self.selectedItem = item;
self.viewMode = SyncPendingViewModeDetails;
[self showDetailsForItem:item];
NSDictionary *raw = item[@"raw"] ?: item;
[self showDetailsForItem:raw];
}
- (CGFloat)tableView:(UITableView *)tableView
......@@ -355,6 +381,7 @@ heightForHeaderInSection:(NSInteger)section {
NSLog(@"Share triggered (stub)");
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
......
// SyncProjectViewController.mm
#import "SyncProjectViewController.h"
#import "LocalStorage.h"
#import "APIClient.h"
#import "NetworkManager.h"
#import "OfflineSyncManager.h"
typedef NS_ENUM(NSInteger, SyncProjectScreenState) {
SyncProjectScreenStateOfflineUnit,
......@@ -14,6 +17,7 @@ static NSString * const kProjectListKey = @"PROJECT_LIST";
@interface SyncUnit : NSObject
@property (nonatomic, strong) NSString *planId;
@property (nonatomic, strong) NSString *displayName;
@property (nonatomic, strong) NSDictionary *project;
@property (nonatomic, strong) NSString *projectId;
@property (nonatomic, strong) NSString *projectName;
@property (nonatomic, assign) BOOL selected;
......@@ -60,13 +64,14 @@ UICollectionViewDataSource
@property (nonatomic, assign) BOOL hasChanges;
@property (nonatomic, strong) NSMutableArray<SyncUnit *> *selectedUnits;
@property (nonatomic, strong) NSMutableArray<SyncProject *> *dummyProjects;
@property (nonatomic, strong) NSMutableArray<SyncUnit *> *dummyUnits;
@property (nonatomic, strong) UITableView *offlineTableView;
@property (nonatomic, strong) UITableView *unitTableView;
@property (nonatomic, strong) UICollectionView *projectCollectionView;
@property (nonatomic, strong) NSMutableArray<SyncProject *> *projects;
@property (nonatomic, assign) BOOL isLoadingProjects;
@property (nonatomic, strong) SyncProject *currentProject;
@end
......@@ -74,11 +79,19 @@ UICollectionViewDataSource
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor systemGroupedBackgroundColor];
self.selectedUnitCount = 0;
self.projects = [NSMutableArray array];
self.selectedUnits = [NSMutableArray array];
self.loadingView =
[[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
self.loadingView.center = self.view.center;
self.loadingView.hidesWhenStopped = YES;
[self.view addSubview:self.loadingView];
[self loadSelectedUnits];
[self setupDummyData];
[self loadProjectList];
[self setupHeader];
[self setupContentContainers];
......@@ -86,7 +99,6 @@ UICollectionViewDataSource
[self setupSelectProjectView];
[self setupSelectUnitView];
[self showOfflineUnit];
[self setScreenState:SyncProjectScreenStateOfflineUnit];
}
......@@ -323,13 +335,13 @@ UICollectionViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.offlineTableView) {
return self.dummyUnits.count;
return self.selectedUnits.count;
}
if (tableView == self.unitTableView) {
return self.dummyUnits.count;
NSLog(@"Units count = %lu", (unsigned long)self.currentProject.units.count);
return self.currentProject.units.count;
}
return 0;
}
......@@ -346,7 +358,14 @@ UICollectionViewDataSource
forIndexPath:indexPath];
}
SyncUnit *unit = self.dummyUnits[indexPath.row];
SyncUnit *unit;
if (tableView == self.offlineTableView) {
unit = self.selectedUnits[indexPath.row];
} else {
unit = self.currentProject.units[indexPath.row];
}
cell.textLabel.text = unit.displayName;
cell.accessoryType = unit.selected
? UITableViewCellAccessoryCheckmark
......@@ -360,18 +379,23 @@ UICollectionViewDataSource
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SyncUnit *unit = self.dummyUnits[indexPath.row];
SyncUnit *unit;
if (tableView == self.offlineTableView) {
unit = self.selectedUnits[indexPath.row];
} else {
unit = self.currentProject.units[indexPath.row];
}
[self toggleUnit:unit];
[tableView reloadRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadData];
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
return self.dummyProjects.count;
return self.projects.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
......@@ -393,12 +417,165 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[cell.contentView addSubview:label];
}
SyncProject *project = self.dummyProjects[indexPath.item];
SyncProject *project = self.projects[indexPath.item];
label.text = project.projectName;
return cell;
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
self.currentProject = self.projects[indexPath.item];
[self showSelectUnit];
}
#pragma mark - Project Data
- (void)loadProjectList {
NSArray *storedProjects =
[LocalStorage loadArrayForKey:kProjectListKey];
if (storedProjects && storedProjects.count > 0) {
NSLog(@"📦 Loaded PROJECT_LIST from storage");
[self parseAndApplyProjects:storedProjects];
return;
}
NSLog(@"🌐 No cached PROJECT_LIST, fetching from API");
[self fetchProjectList];
}
- (void)fetchProjectList {
[self showLoading:YES];
[APIClient fetchProjectList:^(NSDictionary *data, NSError *error) {
// debug
NSString *plainText = [NSString stringWithFormat:@"%@", data];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"projectList.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 projectList saved as plain text to: %@", filePath);
[self showLoading:NO];
if (error || !data) {
NSLog(@"❌ Project list API failed: %@", error);
return;
}
// RN parity: status_code check
if (data[@"status_code"]) {
NSLog(@"❌ API status error: %@", data[@"message"]);
return;
}
NSDictionary *appData = data[@"AppData"];
if (![appData[@"status"] isEqualToString:@"success"]) {
NSLog(@"❌ API failure: %@", appData[@"message"]);
return;
}
NSArray *projects = data[@"Data"];
if (![projects isKindOfClass:[NSArray class]]) {
NSLog(@"❌ Invalid project list format");
return;
}
// RN parity: save then reload from storage
[LocalStorage saveArray:projects forKey:kProjectListKey];
NSLog(@"✅ PROJECT_LIST saved to storage");
[self parseAndApplyProjects:projects];
}];
}
- (void)parseAndApplyProjects:(NSArray *)rawProjects {
[self.projects removeAllObjects];
for (NSDictionary *dict in rawProjects) {
SyncProject *project = [SyncProject new];
project.projectId = [dict[@"id"] description];
project.projectName = dict[@"project_name"];
NSMutableArray *units = [NSMutableArray array];
for (NSDictionary *unitDict in dict[@"unit"]) {
SyncUnit *unit = [SyncUnit new];
unit.planId = [unitDict[@"id"] description];
NSString *name = unitDict[@"name"];
if (!name || name.length == 0) {
NSMutableArray *parts = [NSMutableArray array];
if (unitDict[@"block"]) [parts addObject:unitDict[@"block"]];
if (unitDict[@"level"]) [parts addObject:unitDict[@"level"]];
if (unitDict[@"unit"]) [parts addObject:unitDict[@"unit"]];
name = [parts componentsJoinedByString:@"-"];
}
unit.displayName = name;
unit.projectId = project.projectId;
unit.projectName = project.projectName;
unit.project = @{
@"id": project.projectId,
@"project_name": project.projectName
};
unit.selected = NO;
[units addObject:unit];
}
project.units = units;
[self.projects addObject:project];
}
// Apply stored selections AFTER parsing
[self applyStoredSelections];
// Refresh UI
[self.projectCollectionView reloadData];
[self.offlineTableView reloadData];
}
- (void)applyStoredSelections {
NSArray *stored =
[LocalStorage loadArrayForKey:kSyncSelectedUnitsKey];
if (!stored) return;
for (NSDictionary *dict in stored) {
NSString *planId = dict[@"plan_id"];
NSDictionary *project = dict[@"project"];
NSString *projectId = project[@"id"];
for (SyncProject *p in self.projects) {
if (![p.projectId isEqualToString:projectId]) continue;
for (SyncUnit *u in p.units) {
if ([u.planId isEqualToString:planId]) {
u.selected = YES;
if (![self isUnitAlreadySelected:u]) {
[self.selectedUnits addObject:u];
}
break;
}
}
}
}
self.selectedUnitCount = self.selectedUnits.count;
}
#pragma mark - Screen State
- (void)setScreenState:(SyncProjectScreenState)state {
_screenState = state;
......@@ -449,9 +626,8 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (void)showOfflineUnit {
[self hideAllContent];
[self setScreenState:SyncProjectScreenStateOfflineUnit];
[self setHeaderTitle:@"Offline Unit"];
self.offlineUnitView.hidden = NO;
}
- (void)showSelectProject {
......@@ -461,9 +637,8 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (void)showSelectUnit {
[self hideAllContent];
[self setScreenState:SyncProjectScreenStateSelectUnit];
[self setHeaderTitle:@"Add Unit"];
self.selectUnitView.hidden = NO;
}
- (void)onAddUnit {
......@@ -471,6 +646,11 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (void)onDone {
NSLog(@"Selected units count: %lu",
(unsigned long)self.selectedUnits.count);
[LocalStorage saveArray:self.selectedUnits
forKey:@"SELECTED_OFFLINE_UNITS"];
[self showOfflineUnit];
}
......@@ -479,17 +659,26 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (void)toggleUnit:(SyncUnit *)unit {
if (unit.selected) {
unit.selected = NO;
[self.selectedUnits removeObject:unit];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"planId != %@", unit.planId];
self.selectedUnits =
[[self.selectedUnits filteredArrayUsingPredicate:predicate] mutableCopy];
} else {
if (![self canAddUnit]) {
NSLog(@"only 10 units recommended");
return;
}
if (![self isUnitAlreadySelected:unit]) {
unit.selected = YES;
[self.selectedUnits addObject:unit];
}
}
self.hasChanges = YES;
self.selectedUnitCount = self.selectedUnits.count;
......@@ -547,45 +736,21 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
for (NSDictionary *dict in stored) {
SyncUnit *unit = [self unitFromDictionary:dict];
if (![self isUnitAlreadySelected:unit]) {
[self.selectedUnits addObject:unit];
}
}
self.selectedUnitCount = self.selectedUnits.count;
}
- (void)setupDummyData {
// Dummy Units
self.dummyUnits = [NSMutableArray array];
for (int i = 1; i <= 12; i++) {
SyncUnit *unit = [SyncUnit new];
unit.planId = [NSString stringWithFormat:@"P%02d", i];
unit.displayName = [NSString stringWithFormat:@"Unit %d-A", i];
unit.projectId = [NSString stringWithFormat:@"PRJ%02d", i];
unit.projectName = [NSString stringWithFormat:@"Project %d", i];
unit.selected = i <= 3; // preselect first 3 units
[self.dummyUnits addObject:unit];
}
// Dummy Projects
self.dummyProjects = [NSMutableArray array];
for (int i = 1; i <= 5; i++) {
SyncProject *project = [SyncProject new];
project.projectId = [NSString stringWithFormat:@"PRJ%02d", i];
project.projectName = [NSString stringWithFormat:@"Project %d", i];
NSMutableArray *units = [NSMutableArray array];
for (int j = 1; j <= 3; j++) {
SyncUnit *unit = [SyncUnit new];
unit.planId = [NSString stringWithFormat:@"PRJ%02d-U%d", i, j];
unit.displayName = [NSString stringWithFormat:@"Unit %d-%d", i, j];
unit.projectId = project.projectId;
unit.projectName = project.projectName;
unit.selected = NO;
[units addObject:unit];
- (BOOL)isUnitAlreadySelected:(SyncUnit *)unit {
for (SyncUnit *u in self.selectedUnits) {
if ([u.planId isEqualToString:unit.planId]) {
return YES;
}
project.units = units;
[self.dummyProjects addObject:project];
}
return NO;
}
@end
......@@ -6,6 +6,7 @@
#import "SyncPendingViewController.h"
#import "SyncProjectViewController.h"
#import "SyncEngine.h"
#import "LocalStorage.h"
@interface SyncScreenViewController ()
......@@ -64,7 +65,7 @@
self.isSyncing = NO;
//placeholders
self.pendingItemCount = 1;
self.pendingItemCount = 0;
self.offlineUnitCount = 1;
self.historyCount = 1;
......@@ -75,6 +76,14 @@
[self refreshUI];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.pendingItemCount = [self loadPendingItemCount];
self.offlineUnitCount = [self loadOfflineUnitCount];
[self refreshUI];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self
......@@ -468,7 +477,7 @@
}
- (void)onOfflineUnitsTapped {
if (self.offlineUnitCount == 0 || self.isOfflineMode) return;
if (self.offlineUnitCount == 0) return;
NSLog(@"Offline Units tapped");
SyncProjectViewController *nextVC = [[SyncProjectViewController alloc] init];
......@@ -477,7 +486,7 @@
}
- (void)onPendingItemsTapped {
if (self.pendingItemCount == 0 || self.isOfflineMode) return;
if (self.pendingItemCount == 0) return;
NSLog(@"Pending Items tapped");
SyncPendingViewController *nextVC = [[SyncPendingViewController alloc] init];
......@@ -486,7 +495,7 @@
}
- (void)onHistoryTapped {
if (self.historyCount == 0 || self.isOfflineMode) return;
if (self.historyCount == 0) return;
NSLog(@"History tapped");
SyncHistoryViewController *nextVC = [[SyncHistoryViewController alloc] init];
......@@ -588,4 +597,14 @@
[self refreshUI];
}
- (NSInteger)loadPendingItemCount {
NSArray *queue = [LocalStorage loadArrayForKey:@"OFFLINE_SYNC_QUEUE"];
return queue.count;
}
- (NSInteger)loadOfflineUnitCount {
NSArray *units = [LocalStorage loadArrayForKey:@"SELECTED_OFFLINE_UNITS"];
return units.count;
}
@end
......@@ -10,23 +10,27 @@ NS_ASSUME_NONNULL_BEGIN
// Queue actions
- (void)queueAddIssue:(NSDictionary *)payload
display:(NSDictionary *)display
images:(NSArray<UIImage *> *)images;
- (void)queueUpdateIssue:(NSDictionary *)payload
display:(NSDictionary *)display
images:(NSArray<UIImage *> *)images;
- (void)queueAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
displayInfo:(NSDictionary *)displayInfo
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL * _Nullable)mediaURL
mediaType:(NSString * _Nullable)mediaType;
// Sync
- (void)processQueueIfOnline;
// Helpers
- (BOOL)hasPendingItems;
- (NSArray *)pendingItemsForUI;
- (void)clearOfflineQueue;
@end
NS_ASSUME_NONNULL_END
......@@ -6,6 +6,8 @@
static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
static NSString * const kSelectedOfflineUnitsKey = @"SELECTED_OFFLINE_UNITS";
@implementation OfflineSyncManager
#pragma mark - Singleton
......@@ -15,32 +17,19 @@ static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
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];
NSLog(@"📦 [OfflineSync] Raw stored queue: %@", stored);
if (![stored isKindOfClass:[NSArray class]]) {
NSLog(@"🧹 [OfflineSync] Queue corrupted — resetting");
[LocalStorage removeObjectForKey:kOfflineQueueKey];
......@@ -53,6 +42,9 @@ static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
[validItems addObject:obj];
}
}
NSLog(@"📦 [OfflineSync] Valid queue items: %@", validItems);
return validItems;
}
......@@ -63,23 +55,28 @@ static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
#pragma mark - Public Queue APIs
- (void)queueAddIssue:(NSDictionary *)payload
display:(NSDictionary *)display
images:(NSArray<UIImage *> *)images {
[self enqueueAction:@"ADD_ISSUE"
payload:payload
images:images];
images:images
displayInfo:display];
}
- (void)queueUpdateIssue:(NSDictionary *)payload
display:(NSDictionary *)display
images:(NSArray<UIImage *> *)images {
[self enqueueAction:@"UPDATE_ISSUE"
payload:payload
images:images];
images:images
displayInfo:display];
}
- (void)queueAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
displayInfo:(NSDictionary *)displayInfo
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL * _Nullable)mediaURL
mediaType:(NSString * _Nullable)mediaType {
......@@ -92,14 +89,25 @@ static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
NSMutableDictionary *item = [NSMutableDictionary dictionary];
item[@"id"] = NSUUID.UUID.UUIDString;
item[@"type"] = @"ADD_INFO";
// 🔥 API payload
item[@"issue_id"] = issueID ?: @"";
item[@"remarks"] = remarks ?: @"";
// 🔥 Files
item[@"imagePaths"] = imagePaths ?: @[];
item[@"timestamp"] = @((NSInteger)[NSDate date].timeIntervalSince1970);
if (mediaPath) item[@"mediaPath"] = mediaPath;
if (mediaType) item[@"mediaType"] = mediaType;
// 🔥 UI display fields (CRITICAL)
item[@"projectName"] = displayInfo[@"projectName"] ?: @"Unknown Project";
item[@"unitName"] = displayInfo[@"unitName"] ?: @"-";
item[@"createdBy"] = displayInfo[@"createdBy"] ?: @"-";
item[@"issueDisplay"] = displayInfo[@"issueDisplay"] ?: @"Add Info";
item[@"createdAt"] = displayInfo[@"createdAt"] ?: [self todayDateString];
[queue addObject:item];
[self saveQueue:queue];
......@@ -110,18 +118,25 @@ static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
- (void)enqueueAction:(NSString *)type
payload:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images {
images:(NSArray<UIImage *> *)images
displayInfo:(NSDictionary *)displayInfo {
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)
};
NSMutableDictionary *item = [NSMutableDictionary dictionary];
item[@"id"] = NSUUID.UUID.UUIDString;
item[@"type"] = type;
item[@"payload"] = payload ?: @{};
item[@"imagePaths"] = imagePaths ?: @[];
item[@"timestamp"] = @((NSInteger)[NSDate date].timeIntervalSince1970);
// 🔥 UI fields (OG-style)
item[@"projectName"] = displayInfo[@"projectName"] ?: @"Unknown Project";
item[@"unitName"] = displayInfo[@"unitName"] ?: @"-";
item[@"createdBy"] = displayInfo[@"createdBy"] ?: @"-";
item[@"issueDisplay"] = displayInfo[@"issueDisplay"] ?: type;
item[@"createdAt"] = displayInfo[@"createdAt"] ?: [self todayDateString];
[queue addObject:item];
[self saveQueue:queue];
......@@ -129,30 +144,63 @@ static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
NSLog(@"📥 [OfflineSync] Queued %@ (%lu)", type, (unsigned long)queue.count);
}
#pragma mark - Processing Queue
- (void)processQueueIfOnline {
- (void)startManualSync {
if (![NetworkManager.sharedManager isConnected]) {
NSLog(@"🚫 [OfflineSync] No internet — aborting manual sync");
return;
}
[self processNextItem];
}
- (void)processNextItem {
NSMutableArray *queue = [self safeLoadQueue];
if (queue.count == 0) return;
if (queue.count == 0) {
NSLog(@"✅ [OfflineSync] Queue empty — sync complete");
return;
}
NSDictionary *item = queue.firstObject;
if (![item isKindOfClass:[NSDictionary class]]) {
NSLog(@"⚠️ [OfflineSync] Invalid item — removing");
[queue removeObjectAtIndex:0];
[self saveQueue:queue];
[self processQueueIfOnline];
[self processNextItem];
return;
}
NSLog(@"🔄 [OfflineSync] Processing %@", item[@"type"]);
[self processItem:item completion:^(BOOL success) {
if (success) {
[queue removeObjectAtIndex:0];
[self saveQueue:queue];
[self processQueueIfOnline];
NSLog(@"✅ [OfflineSync] Item synced");
// 🔥 cleanup files
for (NSString *path in item[@"imagePaths"]) {
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}
if (item[@"mediaPath"]) {
[[NSFileManager defaultManager] removeItemAtPath:item[@"mediaPath"] error:nil];
}
// remove item and continue
NSMutableArray *updatedQueue = [self safeLoadQueue];
if (updatedQueue.count > 0) {
[updatedQueue removeObjectAtIndex:0];
[self saveQueue:updatedQueue];
}
[self processNextItem]; // move to next
} else {
NSLog(@"❌ [OfflineSync] Sync failed — stopping");
// ❗ DO NOT remove item
// user must tap Sync again
}
}];
}
......@@ -258,9 +306,61 @@ static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
}
#pragma mark - Status
- (BOOL)hasPendingItems {
return ([self safeLoadQueue].count > 0);
}
#pragma mark - Pending Screen Helper
- (NSArray *)pendingItemsForUI {
NSArray *queue = [self safeLoadQueue];
NSMutableDictionary *grouped = [NSMutableDictionary dictionary];
for (NSDictionary *item in queue) {
NSString *project = item[@"projectName"] ?: @"Unknown Project";
NSMutableArray *items = grouped[project] ?: [NSMutableArray array];
NSDictionary *uiItem = @{
@"unitName": item[@"unitName"] ?: @"-",
@"createdBy": item[@"createdBy"] ?: @"-",
@"createdOn": item[@"createdAt"] ?: [self formatTimestamp:item[@"timestamp"]],
@"type": item[@"issueDisplay"] ?: item[@"type"] ?: @"-",
@"raw": item
};
[items addObject:uiItem];
grouped[project] = items;
}
NSMutableArray *sections = [NSMutableArray array];
for (NSString *project in grouped) {
[sections addObject:@{
@"project": project,
@"items": grouped[project]
}];
}
return sections;
}
- (NSString *)formatTimestamp:(NSNumber *)ts {
NSDate *date = [NSDate dateWithTimeIntervalSince1970:ts.doubleValue];
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = @"yyyy-MM-dd";
return [fmt stringFromDate:date];
}
- (NSString *)todayDateString {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"yyyy-MM-dd";
return [formatter stringFromDate:[NSDate date]];
}
- (void)clearOfflineQueue {
[LocalStorage removeObjectForKey:kOfflineQueueKey];
NSLog(@"🧹 [OfflineSync] Offline queue cleared");
}
@end
......@@ -8,32 +8,32 @@
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
......
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