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
clientID:clientID
clientCode:clientCode
userToken:userToken];
// 4. Back-reference
if ([drawerController.mainViewController isKindOfClass:[DashboardViewController class]]) {
((DashboardViewController *)drawerController.mainViewController).drawerController =
drawerController;
}
DrawerController *drawerController = [[DrawerController alloc]
initWithDrawerViewController:drawer
clientID:clientID
clientCode:clientCode
userToken:userToken];
// 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;
......@@ -101,36 +103,49 @@
[stack.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-16],
[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
// 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
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