Commit 87014f12 authored by Wei Han's avatar Wei Han

code update

parent 09b0eb2e
......@@ -7,9 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
09422FEEE292BF7FDB944B44 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7646DEA673059A6FBED034D5 /* libPods-example.a */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
705C78A9CBF3D37B370DC92C /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CB77023451E7FACEB5CCC67A /* libPods-example.a */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
/* End PBXBuildFile 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>"; };
31E3569C4DACA1583750BB4B /* 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>"; };
67CF26DD341753F546BD183F /* 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>"; };
5350E2781CCDA8D204AD92D7 /* 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>"; };
66DA0C9F0E73892891015312 /* 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>"; };
7646DEA673059A6FBED034D5 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = "<group>"; };
CB77023451E7FACEB5CCC67A /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
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 = (
705C78A9CBF3D37B370DC92C /* libPods-example.a in Frameworks */,
09422FEEE292BF7FDB944B44 /* libPods-example.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -55,7 +55,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
CB77023451E7FACEB5CCC67A /* libPods-example.a */,
7646DEA673059A6FBED034D5 /* libPods-example.a */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
31E3569C4DACA1583750BB4B /* Pods-example.debug.xcconfig */,
67CF26DD341753F546BD183F /* Pods-example.release.xcconfig */,
5350E2781CCDA8D204AD92D7 /* Pods-example.debug.xcconfig */,
66DA0C9F0E73892891015312 /* Pods-example.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -105,13 +105,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = (
0C0B6164CF1596DDFC4F19C5 /* [CP] Check Pods Manifest.lock */,
1A63BC39C4970FBC7B74189D /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
1784E64423995B1E7F285F50 /* [CP] Embed Pods Frameworks */,
6A78F44E33668E0CBF927584 /* [CP] Copy Pods Resources */,
4C643B981BE7FEF41566F449 /* [CP] Embed Pods Frameworks */,
4DB8EF54BF2FC8DAD832C9D8 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -183,7 +183,7 @@
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";
};
0C0B6164CF1596DDFC4F19C5 /* [CP] Check Pods Manifest.lock */ = {
1A63BC39C4970FBC7B74189D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -205,7 +205,7 @@
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;
};
1784E64423995B1E7F285F50 /* [CP] Embed Pods Frameworks */ = {
4C643B981BE7FEF41566F449 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -222,7 +222,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
6A78F44E33668E0CBF927584 /* [CP] Copy Pods Resources */ = {
4DB8EF54BF2FC8DAD832C9D8 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 31E3569C4DACA1583750BB4B /* Pods-example.debug.xcconfig */;
baseConfigurationReference = 5350E2781CCDA8D204AD92D7 /* Pods-example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......@@ -283,7 +283,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 67CF26DD341753F546BD183F /* Pods-example.release.xcconfig */;
baseConfigurationReference = 66DA0C9F0E73892891015312 /* Pods-example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......
......@@ -632,15 +632,15 @@
_currentY = 16.0;
// === PROJECT UPDATES ===
NSArray *announcements = self.announcements;
if (announcements.count > 0) {
UIView *announcementSection = [self buildAnnouncementSection:announcements];
CGRect f = announcementSection.frame;
f.origin.y = _currentY;
announcementSection.frame = f;
[_contentContainer addSubview:announcementSection];
_currentY += CGRectGetHeight(announcementSection.frame) + 12;
}
// NSArray *announcements = self.announcements;
// if (announcements.count > 0) {
// UIView *announcementSection = [self buildAnnouncementSection:announcements];
// CGRect f = announcementSection.frame;
// f.origin.y = _currentY;
// announcementSection.frame = f;
// [_contentContainer addSubview:announcementSection];
// _currentY += CGRectGetHeight(announcementSection.frame) + 12;
// }
// === APPOINTMENTS ===
NSArray *appointments = mainData[@"appointment"];
......@@ -668,7 +668,8 @@
_scrollView.contentSize = CGSizeMake(_scrollView.bounds.size.width, _currentY);
NSLog(@"✅ Dashboard UI refreshed with %lu announcements, %lu appointments, %lu issues",
(unsigned long)announcements.count, (unsigned long)appointments.count, (unsigned long)issues.count);
// (unsigned long)announcements.count,
(unsigned long)appointments.count, (unsigned long)issues.count);
}
- (UIView *)buildAnnouncementSection:(NSArray *)announcements {
......
......@@ -84,7 +84,7 @@ NSString * const DrawerDidSelectItemNotification = @"DrawerDidSelectItemNotifica
[self.drawerVC didMoveToParentViewController:self];
self.drawerLeadingConstraint =
[self.drawerVC.view.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:-self.drawerWidth];
[self.drawerVC.view.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:self.drawerWidth];
[self.drawerVC.view.widthAnchor constraintEqualToConstant:self.drawerWidth].active = YES;
[self.drawerVC.view.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
......@@ -123,11 +123,10 @@ NSString * const DrawerDidSelectItemNotification = @"DrawerDidSelectItemNotifica
#pragma mark - Drawer Actions
- (void)openDrawer {
// Make sure overlay and drawer are on top
[self.view bringSubviewToFront:self.overlayView];
[self.view bringSubviewToFront:self.drawerVC.view];
self.drawerLeadingConstraint.constant = 0;
self.drawerLeadingConstraint.constant = 0; // moves drawer fully into view
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
......@@ -138,7 +137,7 @@ NSString * const DrawerDidSelectItemNotification = @"DrawerDidSelectItemNotifica
}
- (void)closeDrawer {
self.drawerLeadingConstraint.constant = -self.drawerWidth;
self.drawerLeadingConstraint.constant = self.drawerWidth; // moves drawer offscreen
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
......@@ -167,12 +166,13 @@ NSString * const DrawerDidSelectItemNotification = @"DrawerDidSelectItemNotifica
startX = self.mainVC.view.transform.tx;
} else if (pan.state == UIGestureRecognizerStateChanged) {
CGFloat newTx = startX + x;
newTx = MAX(0, newTx);
newTx = MIN(self.drawerWidth, newTx);
// Clamp between 0 (open) and -drawerWidth (closed)
newTx = MIN(0, newTx);
newTx = MAX(-self.drawerWidth, newTx);
CGFloat progress = newTx / self.drawerWidth;
CGFloat progress = fabs(newTx / self.drawerWidth);
self.mainVC.view.transform = CGAffineTransformMakeTranslation(newTx, 0);
self.drawerLeadingConstraint.constant = -self.drawerWidth + newTx;
self.drawerLeadingConstraint.constant = newTx; // trailing anchor moves from +drawerWidth → 0
[self.view layoutIfNeeded];
self.overlayView.alpha = progress;
} else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
......@@ -181,9 +181,9 @@ NSString * const DrawerDidSelectItemNotification = @"DrawerDidSelectItemNotifica
BOOL shouldOpen = NO;
if (fabs(velocityX) > 400) {
shouldOpen = (velocityX > 0);
shouldOpen = (velocityX < 0); // left swipe opens drawer
} else {
shouldOpen = currentTx > (self.drawerWidth * 0.45);
shouldOpen = fabs(currentTx) > (self.drawerWidth * 0.45);
}
if (shouldOpen) [self openDrawer];
......
// ProjectViewController.h
#import <UIKit/UIKit.h>
#import "DrawerController.h"
@interface ProjectViewController : UIViewController
@property (nonatomic, weak) DrawerController *drawerController;
@property (nonatomic, copy) NSString *ClientID;
@property (nonatomic, copy) NSString *ClientCode;
@property (nonatomic, copy) NSString *user_token;
@end
// ProjectViewController.mm
#import "ProjectViewController.h"
@interface ProjectViewController ()
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UILabel *titleLabel;
@end
@implementation ProjectViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemBackgroundColor;
[self setupHeader];
}
#pragma mark - Header Setup
- (void)setupHeader {
self.headerView = [[UIView alloc] init];
self.headerView.backgroundColor = [UIColor colorWithRed:0.0 green:0.5 blue:0.6 alpha:1.0];
self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.headerView];
UIButton *menuButton = [UIButton buttonWithType:UIButtonTypeCustom];
menuButton.translatesAutoresizingMaskIntoConstraints = NO;
[menuButton setImage:[UIImage systemImageNamed:@"line.horizontal.3"] forState:UIControlStateNormal];
menuButton.tintColor = UIColor.whiteColor;
[menuButton addTarget:self action:@selector(handleMenuButtonTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:menuButton];
[NSLayoutConstraint activateConstraints:@[
[self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerView.heightAnchor constraintEqualToConstant:48],
[menuButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-16],
[menuButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[menuButton.widthAnchor constraintEqualToConstant:26],
[menuButton.heightAnchor constraintEqualToConstant:26],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
]];
}
#pragma mark - Helper
- (void)handleMenuButtonTapped {
NSLog(@"🍔 Menu button tapped");
if (self.drawerController) {
[self.drawerController toggleDrawer];
} else {
NSLog(@"⚠️ drawerController not set on DashboardViewController");
}
}
@end
// UnitViewController.h
#import <UIKit/UIKit.h>
#import "DrawerController.h"
@interface UnitViewController : UIViewController
@property (nonatomic, weak) DrawerController *drawerController;
@property (nonatomic, copy) NSString *ClientID;
@property (nonatomic, copy) NSString *ClientCode;
@property (nonatomic, copy) NSString *user_token;
@end
// UnitViewController.mm
#import "UnitViewController.h"
@interface UnitViewController ()
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UILabel *titleLabel;
@end
@implementation UnitViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemBackgroundColor;
[self setupHeader];
}
#pragma mark - Header Setup
- (void)setupHeader {
self.headerView = [[UIView alloc] init];
self.headerView.backgroundColor = [UIColor colorWithRed:0.0 green:0.5 blue:0.6 alpha:1.0];
self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.headerView];
self.backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"]
forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.backButton];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Unit";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:18];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.titleLabel];
UIButton *menuButton = [UIButton buttonWithType:UIButtonTypeCustom];
menuButton.translatesAutoresizingMaskIntoConstraints = NO;
[menuButton setImage:[UIImage systemImageNamed:@"line.horizontal.3"] forState:UIControlStateNormal];
menuButton.tintColor = UIColor.whiteColor;
[menuButton addTarget:self action:@selector(handleMenuButtonTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:menuButton];
[NSLayoutConstraint activateConstraints:@[
[self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerView.heightAnchor constraintEqualToConstant:48],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:8],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.backButton.widthAnchor constraintEqualToConstant:32],
[self.backButton.heightAnchor constraintEqualToConstant:32],
[menuButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-16],
[menuButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[menuButton.widthAnchor constraintEqualToConstant:26],
[menuButton.heightAnchor constraintEqualToConstant:26],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
]];
}
#pragma mark - Helper
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)handleMenuButtonTapped {
NSLog(@"🍔 Menu button tapped");
if (self.drawerController) {
[self.drawerController toggleDrawer];
} else {
NSLog(@"⚠️ drawerController not set on DashboardViewController");
}
}
@end
......@@ -4,35 +4,45 @@
#import "DrawerViewController.h"
#import "DrawerController.h"
#import "NetworkManager.h"
#import "UnitViewController.h"
#import "ProjectViewController.h"
@implementation QmsPluginUI
+ (UIViewController *)makeViewController {
// 0. Start network monitoring immediately
[NetworkManager sharedManager];
// 1. Create drawer
// 1️⃣ Drawer menu
DrawerViewController *drawer = [[DrawerViewController alloc] init];
drawer.profileName = @"placeholder";
drawer.profileEmail = @"placeholder";
// 2. Client props
// 2️⃣ Screens
ProjectViewController *project = [[ProjectViewController alloc] init]; // Landing page
UnitViewController *unit = [[UnitViewController alloc] init]; // Secondary page
DashboardViewController *dashboard = [[DashboardViewController alloc] init];
// 3️⃣ UINavigationController with landing page
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:project];
// 4️⃣ Create drawer controller wrapping nav
NSString *clientID = @"clientID_loading";
NSString *clientCode = @"clientCode_loading";
NSString *userToken = @"userToken_loading";
// 3. Root container
DrawerController *drawerController =
[[DrawerController alloc] initWithDrawerViewController:drawer
DrawerController *drawerController = [[DrawerController alloc]
initWithDrawerViewController:drawer
clientID:clientID
clientCode:clientCode
userToken:userToken];
// 4. Back-reference
if ([drawerController.mainViewController isKindOfClass:[DashboardViewController class]]) {
((DashboardViewController *)drawerController.mainViewController).drawerController =
drawerController;
}
// 5️⃣ Inject drawerController reference into all screens
project.drawerController = drawerController;
unit.drawerController = drawerController;
dashboard.drawerController = drawerController;
// 6️⃣ Replace mainVC in DrawerController with nav
[drawerController setValue:nav forKey:@"_mainVC"]; // or update your property to accept UINavigationController
return drawerController;
}
......
......@@ -68,6 +68,7 @@
#pragma mark - Detail Content
- (void)setupDetailContent {
// 1️⃣ Scroll view container
UIScrollView *scroll = [[UIScrollView alloc] init];
scroll.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:scroll];
......@@ -89,6 +90,7 @@
[container.widthAnchor constraintEqualToAnchor:scroll.widthAnchor],
]];
// 2️⃣ Stack view
UIStackView *stack = [[UIStackView alloc] init];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 12;
......@@ -102,35 +104,48 @@
[stack.bottomAnchor constraintLessThanOrEqualToAnchor:container.bottomAnchor constant:-16],
]];
// 3️⃣ Unit label
UILabel *unitLabel = [[UILabel alloc] init];
unitLabel.text = self.itemData[@"unitName"];
unitLabel.text = self.itemData[@"unitName"] ?: @"-";
unitLabel.font = [UIFont boldSystemFontOfSize:22];
unitLabel.textColor = UIColor.labelColor;
unitLabel.numberOfLines = 0;
[stack addArrangedSubview:unitLabel];
// spacing
// 4️⃣ Spacer
UIView *spacer = [[UIView alloc] init];
[spacer.heightAnchor constraintEqualToConstant:12].active = YES;
[stack addArrangedSubview:spacer];
// 5️⃣ Status color
NSString *status = self.itemData[@"status"];
UIColor *statusColor = [self colorForStatus:status];
// Add rows
[stack addArrangedSubview:[self makeRow:@"Created Date" value:self.itemData[@"createdOn"] color:nil]];
[stack addArrangedSubview:[self makeRow:@"Created By" value:self.itemData[@"createdBy"] color:nil]];
[stack addArrangedSubview:[self makeRow:@"Uploaded On" value:self.itemData[@"raw"][@"syncedAt"] color:statusColor]];
[stack addArrangedSubview:[self makeRow:@"Action Made" value:self.itemData[@"type"] color:nil]];
[stack addArrangedSubview:[self makeRow:@"Location" value:self.itemData[@"raw"][@"fieldNames"][@"locationName"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Category" value:self.itemData[@"raw"][@"category"][@"categoryName"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Type" value:self.itemData[@"raw"][@"type"][@"typeName"] color:nil]];
[stack addArrangedSubview:[self makeRow:@"Issue" value:self.itemData[@"raw"][@"issueDisplay"][@"issueName"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Comment" value:self.itemData[@"raw"][@"remarks"] ?: @"-" color:nil]];
// 6️⃣ Helper block to safely get field values
NSString *(^safeField)(NSString *) = ^NSString *(NSString *fieldName) {
id fieldNames = self.itemData[@"raw"][@"fieldNames"];
if ([fieldNames isKindOfClass:[NSDictionary class]]) {
id value = fieldNames[fieldName];
if ([value isKindOfClass:[NSString class]]) {
return value;
}
}
return @"-";
};
// 7️⃣ Add rows
[stack addArrangedSubview:[self makeRow:@"Created Date" value:self.itemData[@"createdOn"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Created By" value:self.itemData[@"createdBy"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Uploaded On" value:self.itemData[@"raw"][@"syncedAt"] ?: @"-" color:statusColor]];
[stack addArrangedSubview:[self makeRow:@"Action Made" value:self.itemData[@"type"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Location" value:safeField(@"locationName") color:nil]];
[stack addArrangedSubview:[self makeRow:@"Category" value:safeField(@"categoryName") color:nil]];
[stack addArrangedSubview:[self makeRow:@"Type" value:safeField(@"typeName") color:nil]];
[stack addArrangedSubview:[self makeRow:@"Issue" value:safeField(@"issueName") color:nil]];
[stack addArrangedSubview:[self makeRow:@"Comment" value:self.itemData[@"raw"][@"remarks"] ?: self.itemData[@"raw"][@"payload"][@"comment"] ?: @"-" color:nil]];
}
- (UIView *)makeRow:(NSString *)title
value:(NSString *)value
color:(UIColor *)color {
......
......@@ -392,12 +392,17 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSLog(@"Tapped section %ld row %ld", (long)indexPath.section, (long)indexPath.row);
HistorySyncItem *selectedItem = self.filteredItems[indexPath.row];
NSDictionary *safeData = [self normalizedRow:selectedItem.raw];
if (!safeData) {
NSLog(@"❌ Invalid history item tapped: %@", selectedItem.raw);
return;
}
HistoryDetailsViewController *vc = [HistoryDetailsViewController new];
vc.itemData = selectedItem.raw;
vc.itemData = safeData;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
}
......@@ -450,7 +455,7 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
[self.syncAgainButton.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.syncAgainButton.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.syncAgainButton.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.syncAgainButton.heightAnchor constraintEqualToConstant:30],
[self.syncAgainButton.heightAnchor constraintEqualToConstant:45],
]];
}
......@@ -618,4 +623,23 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
hasFailedItems ? [UIColor systemBlueColor] : [UIColor lightGrayColor];
}
- (NSDictionary *)normalizedRow:(id)row {
if ([row isKindOfClass:[NSDictionary class]]) {
return row;
}
if ([row isKindOfClass:[NSString class]]) {
NSData *data = [(NSString *)row dataUsingEncoding:NSUTF8StringEncoding];
if (!data) return nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([json isKindOfClass:[NSDictionary class]]) {
return json;
}
}
return nil;
}
@end
......@@ -52,6 +52,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
@property (nonatomic, strong) UIButton *downloadUnitsToggle;
@property (nonatomic, strong) SyncProgressOverlay *progressOverlay;
@property (nonatomic, strong) UIButton *yesBtn;
@end
......@@ -329,8 +330,8 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
- (void)showSyncConfirmationModal {
if (self.syncModalOverlay) return; // already showing
self.shouldSubmitPending = YES;
self.shouldDownloadUnits = YES;
self.shouldSubmitPending = NO;
self.shouldDownloadUnits = NO;
[self buildSyncModal];
}
......@@ -377,17 +378,20 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
// --- Toggles ---
self.submitPendingToggle =
[self createToggleWithTitle:@"Submit all pending"
selected:YES
selected:NO
action:@selector(onSubmitPendingToggle)];
self.downloadUnitsToggle =
[self createToggleWithTitle:@"Download all selected units"
selected:YES
selected:NO
action:@selector(onDownloadUnitsToggle)];
// --- Buttons ---
UIButton *noBtn = [self modalButton:@"NO" action:@selector(onSyncModalNo)];
UIButton *yesBtn = [self modalButton:@"YES" action:@selector(onSyncModalYes)];
self.yesBtn = [self modalButton:@"YES" action:@selector(onSyncModalYes)];
self.yesBtn.enabled = self.shouldSubmitPending || self.shouldDownloadUnits;
self.yesBtn.alpha = self.yesBtn.enabled ? 1.0 : 0.5;
// --- Layout ---
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[
......@@ -396,7 +400,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
self.submitPendingToggle,
self.downloadUnitsToggle,
noBtn,
yesBtn
self.yesBtn
]];
stack.axis = UILayoutConstraintAxisVertical;
......@@ -487,12 +491,14 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
self.shouldSubmitPending = !self.shouldSubmitPending;
self.submitPendingToggle.selected = self.shouldSubmitPending;
[self updateToggleAppearance:self.submitPendingToggle title:@"Submit all pending"];
[self updateYesButtonState];
}
- (void)onDownloadUnitsToggle {
self.shouldDownloadUnits = !self.shouldDownloadUnits;
self.downloadUnitsToggle.selected = self.shouldDownloadUnits;
[self updateToggleAppearance:self.downloadUnitsToggle title:@"Download all selected units"];
[self updateYesButtonState];
}
- (void)updateToggleAppearance:(UIButton *)btn title:(NSString *)title {
......@@ -530,15 +536,20 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[[OfflineSyncManager shared]
processNextItemWithProgress:^(NSInteger step, NSInteger total, NSString *title) {
NSLog(@"📊 progress %ld/%ld %@", (long)step, (long)total, title);
[self.progressOverlay updateStep:step
total:total
title:title];
} completion:^(BOOL finished) {
NSLog(@"🎉 Offline sync COMPLETED finished=%d", finished);
self.pendingItemCount = [self loadPendingItemCount];
self.historyCount = [self loadHistoryItemCount];
if (!self.shouldDownloadUnits) {
[self finishSyncWithSuccess:finished];
}
[self refreshUI];
}];
}
......@@ -620,4 +631,11 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
return [formatter stringFromDate:date];
}
- (void)updateYesButtonState {
BOOL enabled = self.shouldSubmitPending || self.shouldDownloadUnits;
self.yesBtn.enabled = enabled;
self.yesBtn.alpha = enabled ? 1.0 : 0.5;
}
@end
// APIClient.mm
#import "APIClient.h"
#import "APIConfig.h"
@implementation APIClient
+ (void)requestAccessItem:(NSString *)unitId
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/plan/getAccessItem?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/plan/getAccessItem"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSDictionary *deviceInfo = @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
NSDictionary *jsonBody = @{
@"data": @{
@"os": @"AND",
@"drawing_plan_id": unitId ?: @"",
@"information": deviceInfo,
@"information": [APIConfig deviceInfo],
}
};
NSLog(@"🌍 SUBMIT URL: %@", urlString);
NSLog(@"🌍 SUBMIT URL: %@", url.absoluteString);
NSLog(@"📦 SUBMIT Body JSON: %@", jsonBody);
NSError *jsonError;
......@@ -82,8 +73,7 @@
+ (void)requestGetUnitPlan:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ Construct URL (with token)
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/plan/getLocationByUnit?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/plan/getLocationByUnit"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -111,7 +101,7 @@
appendFormField(@"data[drawing_plan_id]",@"135");
// You can reuse your existing getDeviceInfo() method to produce this JSON string
NSString *deviceInfo = @"{\"OS\":\"AND\",\"IMEI\":\"AND:2a2f13ff17b1cbb5\",\"OS_VERSION\":\"35\",\"MODEL\":\"2306EPN60G\",\"APP_VERSION\":\"1.22.26\"}";
NSString *deviceInfo = [APIConfig deviceInfoJSONString];
appendFormField(@"data[information]", deviceInfo);
// 🔚 End boundary
......@@ -120,7 +110,7 @@
request.HTTPBody = body;
// 🌍 Debug logs
NSLog(@"🌍 [requestUnitPlan] URL: %@", urlString);
NSLog(@"🌍 [requestUnitPlan] URL: %@", url.absoluteString);
NSLog(@"📦 [requestUnitPlan] Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
// 🌐 Perform request
......@@ -168,8 +158,8 @@
+ (void)requestSettingsByLocation:(NSString *)locationID
projectID:(NSString *)projectID
completion:(void (^)(NSDictionary *data, NSError *error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/issue/getGeneralSettingByLocation?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/plan/getGeneralSettingByLocation"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -197,7 +187,7 @@
appendFormField(@"data[project_id]", projectID ?: @"0");
appendFormField(@"data[location_id]", locationID ?: @"0");
NSString *deviceInfo = @"{\"OS\":\"AND\",\"IMEI\":\"AND:2a2f13ff17b1cbb5\",\"OS_VERSION\":\"35\",\"MODEL\":\"2306EPN60G\",\"APP_VERSION\":\"1.22.26\"}";
NSString *deviceInfo = [APIConfig deviceInfoJSONString];
appendFormField(@"data[information]", deviceInfo);
// End boundary
......@@ -206,7 +196,7 @@
request.HTTPBody = body;
// 🌍 Debug logs
NSLog(@"🌍 [requestSettingsByLocation] URL: %@", urlString);
NSLog(@"🌍 [requestSettingsByLocation] URL: %@", url.absoluteString);
NSLog(@"📦 [requestSettingsByLocation] Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
// 🌐 Send request
......@@ -257,7 +247,7 @@
images:(NSArray<UIImage *> * _Nullable)images
completion:(void (^)(NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/addIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"; NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/addIssue"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -349,7 +339,7 @@
images:(NSArray<UIImage *> * _Nullable)images
completion:(void (^)(NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/updateIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"; NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/updateIssue"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -453,8 +443,7 @@
+ (void)requestAppointment:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/getSlot?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/appointment/getSlot"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -483,7 +472,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -518,8 +507,7 @@
+ (void)requestGeneralInfo:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/plan/getUnitInfo?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/plan/getUnitInfo"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -547,7 +535,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -583,10 +571,9 @@
+ (void)submitAppointment:(NSDictionary *)appointmentData
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/makeAppointment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/appointment/makeAppointment"];
NSLog(@"🌍 SUBMIT (NO REP) URL: %@", urlString);
NSLog(@"🌍 SUBMIT (NO REP) URL: %@", url.absoluteString);
NSLog(@"📦 SUBMIT (NO REP) Body JSON: %@", appointmentData);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
......@@ -643,10 +630,8 @@
+ (void)submitRepresentativeAppointment:(NSDictionary *)repData
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/makeAppointment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(@"🌍 SUBMIT URL: %@", urlString);
NSURL *url = [APIConfig urlWithPath:@"/owner/appointment/makeAppointment"];
NSLog(@"🌍 SUBMIT URL: %@", url.absoluteString);
// Create request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
......@@ -711,8 +696,7 @@
reason:(NSString *)reason
completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/cancelAppointment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/appointment/cancelAppointment"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -741,7 +725,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -775,16 +759,11 @@
}
+ (void)requestDashboardInfo:(void (^)(NSDictionary *data, NSError *error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/plan/getIssueUpdateAppointmentInfo";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/plan/getIssueUpdateAppointmentInfo"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
// 🪪 Authorization header
NSString *token = @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
[request setValue:[NSString stringWithFormat:@"Bearer %@", token] forHTTPHeaderField:@"Authorization"];
// 🧱 Create multipart form-data body
NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
......@@ -811,8 +790,7 @@
request.HTTPBody = body;
// 🌍 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🪪 Authorization: Bearer %@", token);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
......@@ -839,8 +817,7 @@
+ (void)requestAnnouncement:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ Keep token in URL
NSString *urlString = @"https://kitadev.commudesk.com/api/clientAnnouncement?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/clientAnnouncement"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -870,7 +847,7 @@
request.HTTPBody = body;
// 🌍 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
......@@ -909,8 +886,7 @@
+ (void)requestHistory:(NSString *)issueID
completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/getIssueHistory?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/getIssueHistory"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -919,20 +895,11 @@
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// ✅ Build JSON body
NSDictionary *deviceInfo = @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
NSDictionary *jsonBody = @{
@"data": @{
@"os": @"AND",
@"issue_id": issueID,
@"information": deviceInfo
@"information": [APIConfig deviceInfo],
}
};
......@@ -947,7 +914,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -984,8 +951,7 @@
remarks:(NSString *)remarks
completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/addInfo?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/addInfo"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -994,22 +960,13 @@
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// ✅ Build JSON body
NSDictionary *deviceInfo = @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
NSDictionary *jsonBody = @{
@"data": @{
@"issue_id": issueID,
@"remarks": remarks,
@"os": @"AND",
@"full_response": @"false",
@"information": deviceInfo
@"information": [APIConfig deviceInfo],
}
};
......@@ -1024,7 +981,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1046,7 +1003,7 @@
if (jsonParseError) {
NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
} else {
NSLog(@"✅ Issues API Response:\n%@", jsonResponse);
NSLog(@"✅ Add Info API Response:\n%@", jsonResponse);
}
dispatch_async(dispatch_get_main_queue(), ^{
......@@ -1064,7 +1021,7 @@
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];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/addInfo"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1177,8 +1134,7 @@
+ (void)requestIssues:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/getIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/getIssue"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1187,20 +1143,11 @@
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// ✅ Build JSON body
NSDictionary *deviceInfo = @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
NSDictionary *jsonBody = @{
@"data": @{
@"os": @"AND",
@"drawing_plan_id": @"135",
@"information": deviceInfo
@"information": [APIConfig deviceInfo],
}
};
......@@ -1215,7 +1162,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1252,8 +1199,7 @@
remarks:(NSString *)remarks
completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/voidIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/voidIssue"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1289,7 +1235,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1326,8 +1272,7 @@
remarks:(NSString *)remarks
completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/deleteIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/deleteIssue"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1356,7 +1301,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1391,8 +1336,7 @@
+ (void)requestCommonArea:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/plan/getCommonArea?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/plan/getCommonArea"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1401,21 +1345,12 @@
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// ✅ Device info dictionary
NSDictionary *deviceInfo = @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
// ✅ Build JSON body
NSDictionary *jsonBody = @{
@"data": @{
@"os": @"AND",
@"project_id": @"8",
@"information": deviceInfo,
@"information": [APIConfig deviceInfo],
}
};
......@@ -1430,7 +1365,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1465,8 +1400,7 @@
+ (void)requestGeneralSettings:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/issue/getGeneralSetting?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/issue/getGeneralSetting"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1475,21 +1409,12 @@
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// ✅ Device info dictionary
NSDictionary *deviceInfo = @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
// ✅ Build JSON body
NSDictionary *jsonBody = @{
@"data": @{
@"os": @"AND",
@"project_id": @"8",
@"information": deviceInfo,
@"information": [APIConfig deviceInfo]
}
};
......@@ -1504,7 +1429,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1539,8 +1464,7 @@
+ (void)requestDefectMatrix:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/issue/getGeneralSettingByUnit?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/issue/getGeneralSettingByUnit"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1549,22 +1473,13 @@
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// ✅ Device info dictionary
NSDictionary *deviceInfo = @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
// ✅ Build JSON body
NSDictionary *jsonBody = @{
@"data": @{
@"os": @"AND",
@"project_id": @"8",
@"drawing_plan_id": @"135",
@"information": deviceInfo,
@"information": [APIConfig deviceInfo]
}
};
......@@ -1579,7 +1494,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1614,8 +1529,7 @@
+ (void)requestProjectList:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/project/listProject?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/project/listProject"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1642,7 +1556,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1679,8 +1593,7 @@
completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/offline/syncImageArray?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [APIConfig urlWithPath:@"/owner/offline/syncImageArray"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
......@@ -1705,7 +1618,7 @@
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🌍 URL: %@", url.absoluteString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
......@@ -1740,42 +1653,42 @@
// + (void)downloadAndCacheImage:(NSURL *)url
// completion:(void (^)(BOOL success))completion {
//
// if (!url) {
// if (completion) completion(NO);
// return;
// }
//
// // 1️⃣ Build cache path
// NSString *fileName = url.lastPathComponent;
// // NSString *cacheDir = [self offlineImageCacheDirectory];
// NSString *filePath = [cacheDir stringByAppendingPathComponent:fileName];
//
// // 2️⃣ Skip if already cached
// if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
// NSLog(@"🟢 Cached file exists: %@", fileName);
// if (completion) completion(YES);
// return;
// }
//
// // 3️⃣ Build request with timeout
// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// request.HTTPMethod = @"GET";
// request.timeoutInterval = 20; // ⏱️ per-file timeout
//
// NSURLSessionConfiguration *config =
// [NSURLSessionConfiguration defaultSessionConfiguration];
// config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
//
// NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
//
// // 4️⃣ Download task
// NSURLSessionDownloadTask *task =
// [session downloadTaskWithRequest:request
// completionHandler:^(NSURL *location,
// NSURLResponse *response,
// NSError *error) {
//
// if (error || !location) {
// NSLog(@"❌ Download failed: %@ | %@", url, error);
// dispatch_async(dispatch_get_main_queue(), ^{
......@@ -1783,14 +1696,14 @@
// });
// return;
// }
//
// // 5️⃣ Move file to cache
// NSError *moveError;
// [[NSFileManager defaultManager]
// moveItemAtURL:location
// toURL:[NSURL fileURLWithPath:filePath]
// error:&moveError];
//
// if (moveError) {
// NSLog(@"❌ Save failed: %@", moveError);
// dispatch_async(dispatch_get_main_queue(), ^{
......@@ -1798,14 +1711,14 @@
// });
// return;
// }
//
// NSLog(@"✅ Cached: %@", fileName);
//
// dispatch_async(dispatch_get_main_queue(), ^{
// if (completion) completion(YES);
// });
// }];
//
// [task resume];
// }
......
// APIConfig.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface APIConfig : NSObject
/// Base API URL (no trailing slash)
+ (NSString *)baseURL;
/// Auth token (JWT)
+ (NSString *)authToken;
/// Convenience: baseURL + path + ?token=
+ (NSURL *)urlWithPath:(NSString *)path;
/// Dictionary form (for future flexibility)
+ (NSDictionary<NSString *, NSString *> *)deviceInfo;
/// JSON string form (what your APIs expect)
+ (NSString *)deviceInfoJSONString;
@end
NS_ASSUME_NONNULL_END
// APIConfig.mm
#import "APIConfig.h"
@implementation APIConfig
+ (NSString *)baseURL {
return @"https://kitadev.commudesk.com/api";
}
+ (NSString *)authToken {
return @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
}
+ (NSURL *)urlWithPath:(NSString *)path {
NSString *urlString =
[NSString stringWithFormat:@"%@%@?token=%@",
[self baseURL],
path,
[self authToken]];
return [NSURL URLWithString:urlString];
}
+ (NSDictionary<NSString *, NSString *> *)deviceInfo {
return @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
}
+ (NSString *)deviceInfoJSONString {
NSData *data =
[NSJSONSerialization dataWithJSONObject:[self deviceInfo]
options:0
error:nil];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
@end
......@@ -169,20 +169,24 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
__block NSInteger step = 0;
__weak typeof(self) weakSelf = self;
__block void (^processNext)(void);
__weak __block void (^weakProcessNext)(void);
// 🔑 MUST be __block
__block void (^processNextItem)(void);
processNext = ^{
processNextItem = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
NSMutableArray *currentQueue = [strongSelf safeLoadQueue];
if (currentQueue.count == 0) {
if (queue.count == 0) {
NSLog(@"✅ [OfflineSync] Queue empty — finishing");
if (completion) completion(YES);
// 🔑 BREAK the self-retain cycle
processNextItem = nil;
return;
}
NSDictionary *item = currentQueue.firstObject;
NSDictionary *item = queue.firstObject;
step++;
if (progress) {
......@@ -194,14 +198,17 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[strongSelf safeProcessItem:item taskKey:taskKey completion:^(BOOL success) {
__strong typeof(weakSelf) strongSelf2 = weakSelf;
if (!strongSelf2) return;
// Save to history
NSMutableDictionary *historyItem = [item mutableCopy];
historyItem[@"syncedAt"] = [strongSelf todayDateString];
historyItem[@"syncedAt"] = [strongSelf2 todayDateString];
historyItem[@"status"] = success ? @"SUCCESS" : @"FAILED";
NSMutableArray *history = [strongSelf safeLoadHistory];
NSMutableArray *history = [strongSelf2 safeLoadHistory];
[history insertObject:historyItem atIndex:0];
[strongSelf saveHistoryQueue:history];
[strongSelf2 saveHistoryQueue:history];
// Cleanup files
for (NSString *path in item[@"imagePaths"]) {
......@@ -211,22 +218,21 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[[NSFileManager defaultManager] removeItemAtPath:item[@"mediaPath"] error:nil];
}
// Remove processed item from queue
NSMutableArray *updatedQueue = [strongSelf safeLoadQueue];
if (updatedQueue.count > 0) {
[updatedQueue removeObjectAtIndex:0];
[strongSelf saveQueue:updatedQueue];
}
// Remove item
[queue removeObjectAtIndex:0];
[strongSelf2 saveQueue:queue];
// Move to next item
// Next iteration
dispatch_async(dispatch_get_main_queue(), ^{
if (weakProcessNext) weakProcessNext();
if (processNextItem) {
processNextItem();
}
});
}];
};
weakProcessNext = processNext;
processNext();
// Start
processNextItem();
}
- (void)safeProcessItem:(NSDictionary *)item
......@@ -545,22 +551,30 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
NSString *drawingPlanId = @"135";
NSArray *selectedUnits = [LocalStorage loadArrayForKey:kSelectedOfflineUnitsKey] ?: @[];
NSMutableArray *tasks = [NSMutableArray array];
// ------------------------------
// Task 1 — requestIssues
// Build sequential tasks
// ------------------------------
NSMutableArray<void (^)(void (^next)(void))> *tasks = [NSMutableArray array];
// Task 1 — requestIssues
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task1" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"ISSUES_%@", drawingPlanId];
NSLog(@"🚀 Starting Task 1 — requestIssues (key: %@)", key);
[APIClient requestIssues:^(NSDictionary *res, NSError *err) {
NSString *logText = [NSString stringWithFormat:
@"\n🚀 Task 1 — requestIssues\nTime: %@\nError: %@\nResponse: %@\n",
[NSDate date], err, res];
[self appendOfflineLog:logText];
if (err) {
NSLog(@"⚠️ Task 1 failed: %@", err);
logSuccess(NO);
} else {
NSLog(@"✅ Task 1 succeeded");
if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
NSLog(@"✅ Task 1 succeeded, saved to key: %@", key);
logSuccess(YES);
}
safeNext();
......@@ -569,20 +583,24 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
} next:next];
}];
// ------------------------------
// Task 2 — requestDashboardInfo
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task2" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"DASHBOARD_INFO_%@", drawingPlanId];
NSLog(@"🚀 Starting Task 2 — requestDashboardInfo (key: %@)", key);
[APIClient requestDashboardInfo:^(NSDictionary *res, NSError *err) {
NSString *logText = [NSString stringWithFormat:
@"\n🚀 Task 2 — requestDashboardInfo\nTime: %@\nError: %@\nResponse: %@\n",
[NSDate date], err, res];
[self appendOfflineLog:logText];
if (err) {
NSLog(@"⚠️ Task 2 failed: %@", err);
logSuccess(NO);
} else {
NSLog(@"✅ Task 2 succeeded");
if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
NSLog(@"✅ Task 2 succeeded, saved to key: %@", key);
logSuccess(YES);
}
safeNext();
......@@ -591,81 +609,108 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
} next:next];
}];
// ------------------------------
// Task 3 — requestGetUnitPlan
// ------------------------------
// Task 3 — requestGetUnitPlan (already has logs)
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task3" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"PLAN_UNIT_%@", drawingPlanId];
NSLog(@"🚀 Starting Task 3 — requestGetUnitPlan (key: %@)", key);
[APIClient requestGetUnitPlan:^(NSDictionary *res, NSError *err) {
NSString *logText = [NSString stringWithFormat:
@"\n🚀 Task 3 — requestGetUnitPlan\nTime: %@\nError: %@\nResponse: %@\n",
[NSDate date], err, res];
[self appendOfflineLog:logText];
if (err || ![res isKindOfClass:[NSDictionary class]]) {
NSLog(@"⚠️ Task 3 failed: %@", err);
logSuccess(NO);
} else {
safeNext();
return;
}
id dataArray = res[@"Data"];
NSDictionary *objData = @{};
if ([dataArray isKindOfClass:[NSArray class]] && [dataArray count] > 0) {
id firstItem = dataArray[0];
if ([dataArray isKindOfClass:[NSArray class]] && ((NSArray *)dataArray).count > 0) {
id firstItem = ((NSArray *)dataArray)[0];
if ([firstItem isKindOfClass:[NSDictionary class]]) objData = firstItem;
}
[LocalStorage saveDictionary:objData forKey:key];
NSLog(@"✅ Task 3 succeeded, saved to key: %@", key);
logSuccess(YES);
}
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 4 — requestGeneralSettings
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task4" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"SETTINGS_%@", projectId];
NSLog(@"🚀 Starting Task 4 — requestGeneralSettings (key: %@)", key);
[APIClient requestGeneralSettings:^(NSDictionary *res, NSError *err) {
NSString *logText = [NSString stringWithFormat:
@"\n🚀 Task 4 — requestGeneralSettings\nTime: %@\nError: %@\nResponse: %@\n",
[NSDate date], err, res];
[self appendOfflineLog:logText];
if (err) {
NSLog(@"⚠️ Task 4 failed: %@", err);
logSuccess(NO);
} else {
if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
NSLog(@"✅ Task 4 succeeded, saved to key: %@", key);
logSuccess(YES);
NSLog(@"✅ Task 4 succeeded");
}
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 5 — requestCommonArea
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task5" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"COMMONAREA_%@", projectId];
NSLog(@"🚀 Starting Task 5 — requestCommonArea (key: %@)", key);
[APIClient requestCommonArea:^(NSDictionary *res, NSError *err) {
NSString *logText = [NSString stringWithFormat:
@"\n🚀 Task 5 — requestCommonArea\nTime: %@\nError: %@\nResponse: %@\n",
[NSDate date], err, res];
[self appendOfflineLog:logText];
if (err) {
NSLog(@"⚠️ Task 5 failed: %@", err);
logSuccess(NO);
} else {
if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
NSLog(@"✅ Task 5 succeeded, saved to key: %@", key);
logSuccess(YES);
NSLog(@"✅ Task 5 succeeded");
}
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 6 — requestDefectMatrix
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task6" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSLog(@"🚀 Starting Task 6 — requestDefectMatrix");
[APIClient requestDefectMatrix:^(NSDictionary *res, NSError *err) {
NSString *logText = [NSString stringWithFormat:
@"\n🚀 Task 6 — requestDefectMatrix\nTime: %@\nError: %@\nResponse: %@\n",
[NSDate date], err, res];
[self appendOfflineLog:logText];
if (!res || err) {
NSLog(@"⚠️ Task 6 failed: %@", err);
logSuccess(NO);
......@@ -675,6 +720,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
NSDictionary *data = res[@"Data"];
if (![data isKindOfClass:[NSDictionary class]]) {
NSLog(@"⚠️ Task 6 failed: Data invalid");
logSuccess(NO);
safeNext();
return;
......@@ -709,140 +755,114 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
for (id locationId in locationIds) {
NSString *locationKey = [NSString stringWithFormat:@"LOCATION_SETTINGS_%@", locationId];
[LocalStorage saveDictionary:settings forKey:locationKey];
NSLog(@"✅ Task 6 — saved settings for location: %@", locationId);
}
}
}
NSLog(@"✅ Task 6 succeeded");
logSuccess(YES);
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 7 — Download Images
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task7" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
if (selectedUnits.count == 0) {
logSuccess(YES);
safeNext();
return;
}
// Task 7 — Image downloads
if (selectedUnits.count > 0) {
[self fetchOfflineImageListForSelectedUnits:selectedUnits completion:^(NSArray<NSURL *> *urls, NSError *error) {
if (error || urls.count == 0) {
logSuccess(NO);
safeNext();
return;
NSLog(@"⚠️ No images to download");
} else {
// Flatten all images into tasks
for (NSInteger i = 0; i < urls.count; i++) {
NSURL *url = urls[i];
[tasks addObject:^(void (^next)(void)) {
NSString *urlKey = [NSString stringWithFormat:@"image_%ld", (long)i];
__block BOOL finished = NO;
// Timeout guard per image
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!finished) {
NSLog(@"⚠️ Image download timeout: %@", url);
NSMutableDictionary *status =
[[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
status[urlKey] = @"FAILED";
[LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
finished = YES;
if (next) next();
}
});
[self downloadImagesSequentially:urls
progress:^(NSInteger completedImages, NSInteger totalImages) {
NSInteger overallCompleted = 6 + completedImages;
NSInteger overallTotal = 6 + urls.count;
if (progress) progress(overallCompleted, overallTotal);
} next:^{
logSuccess(YES);
safeNext();
}];
[APIClient downloadAndCacheImage:url completion:^(BOOL success) {
if (finished) return;
finished = YES;
NSMutableDictionary *status =
[[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
status[urlKey] = success ? @"SUCCESS" : @"FAILED";
[LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
if (next) next();
}];
} next:next];
}];
}
}
// ------------------------------
// Sequential executor
// Start recursive execution
// ------------------------------
__block NSInteger currentTaskIndex = 0;
__block void (^runNextTask)(void) = nil;
__weak typeof(runNextTask) weakRunNextTask = nil;
__block NSInteger currentIndex = 0;
NSInteger totalTasks = tasks.count;
__block void (^runNextTask)(void);
runNextTask = ^{
__strong typeof(weakRunNextTask) strongRunNextTask = weakRunNextTask;
if (!strongRunNextTask) return;
if (currentTaskIndex >= tasks.count) {
if (currentIndex >= totalTasks) {
[self saveLastOfflineSyncDate];
[self saveOfflineDownloadStatusToFile];
if (completion) completion(YES);
runNextTask = nil;
return;
}
void (^task)(void (^)(void)) = tasks[currentTaskIndex];
currentTaskIndex++;
void (^task)(void (^)(void)) = tasks[currentIndex];
currentIndex++;
if (progress && currentTaskIndex <= 6) {
NSInteger overallTotal = 6 + selectedUnits.count;
progress(currentTaskIndex, overallTotal);
}
if (progress) progress(currentIndex, totalTasks);
dispatch_async(dispatch_get_main_queue(), ^{
task(strongRunNextTask);
task(runNextTask);
});
};
weakRunNextTask = runNextTask;
runNextTask();
}
#pragma mark - Robust Image Download
- (void)downloadImagesSequentially:(NSArray<NSURL *> *)urls
progress:(void (^)(NSInteger completed, NSInteger total))progress
next:(void (^)(void))next {
if (urls.count == 0) {
if (next) next();
}];
return;
}
__block NSInteger index = 0;
NSInteger total = urls.count;
__block void (^downloadNext)(void) = nil;
__weak __block void (^weakDownloadNext)(void) = nil;
// If no selected units, just run tasks 1–6
__block NSInteger currentIndex = 0;
NSInteger totalTasks = tasks.count;
downloadNext = ^{
__strong typeof(weakDownloadNext) strongDownloadNext = weakDownloadNext;
if (!strongDownloadNext) return;
if (index >= total) {
if (next) next();
__block void (^runNextTask)(void);
runNextTask = ^{
if (currentIndex >= totalTasks) {
[self saveLastOfflineSyncDate];
[self saveOfflineDownloadStatusToFile];
if (completion) completion(YES);
runNextTask = nil;
return;
}
NSURL *url = urls[index];
NSString *urlKey = [NSString stringWithFormat:@"image_%ld", (long)index];
index++;
// Timeout guard
__block BOOL finished = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!finished) {
NSLog(@"⚠️ Image download timeout: %@", url);
NSMutableDictionary *status = [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
status[urlKey] = @"FAILED";
[LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
finished = YES;
if (progress) progress(index, total);
strongDownloadNext();
}
});
[APIClient downloadAndCacheImage:url completion:^(BOOL success) {
if (finished) return;
void (^task)(void (^)(void)) = tasks[currentIndex];
currentIndex++;
NSMutableDictionary *status = [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
status[urlKey] = success ? @"SUCCESS" : @"FAILED";
[LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
finished = YES;
if (progress) progress(currentIndex, totalTasks);
if (progress) progress(index, total);
strongDownloadNext();
}];
dispatch_async(dispatch_get_main_queue(), ^{
task(runNextTask);
});
};
weakDownloadNext = downloadNext;
downloadNext();
runNextTask();
}
#pragma mark - Image Download Helpers
......@@ -985,5 +1005,49 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
NSLog(@"============================================\n");
}
- (void)saveOfflineDownloadStatusToFile {
NSMutableDictionary *status = [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:status options:NSJSONWritingPrettyPrinted error:&error];
if (!jsonData) {
NSLog(@"⚠️ Failed to serialize offline download status: %@", error);
return;
}
NSString *documentsDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [documentsDir stringByAppendingPathComponent:@"OfflineDownloadStatus.txt"];
BOOL success = [jsonData writeToFile:filePath options:NSDataWritingAtomic error:&error];
if (success) {
NSLog(@"✅ Offline download status saved to file: %@", filePath);
} else {
NSLog(@"⚠️ Failed to write offline download status to file: %@", error);
}
}
- (NSString *)offlineLogFilePath {
NSString *docsDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsDir stringByAppendingPathComponent:@"OfflineSyncLog.txt"];
NSLog(@"📄 Offline log file path: %@", filePath); // <-- added log
return filePath;
}
- (void)appendOfflineLog:(NSString *)text {
NSString *filePath = [self offlineLogFilePath];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:filePath]) {
[text writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"🆕 Created offline log file at path: %@", filePath); // log when file created
} else {
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[handle seekToEndOfFile];
[handle writeData:[text dataUsingEncoding:NSUTF8StringEncoding]];
[handle closeFile];
NSLog(@"✏️ Appended to offline log file: %@", filePath); // log when appending
}
}
@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