Commit e54ac633 authored by Wei Han's avatar Wei Han

code update

parent cb977eab
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0DB59E54C0AC880864FE2DED /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C76E4DA09BCDA64E6AB1EC44 /* libPods-example.a */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; 44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
5F4A3CC64B2ECE3AC87E1244 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CAD4EDE547AF420693D76574 /* libPods-example.a */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
...@@ -21,9 +21,9 @@ ...@@ -21,9 +21,9 @@
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = example/PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = example/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = example/AppDelegate.swift; sourceTree = "<group>"; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = example/AppDelegate.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = "<group>"; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = "<group>"; };
8A486A6AB07608F834FB5B89 /* 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>"; }; BF80FACDA71FF077CAE23907 /* 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>"; };
917B7F7F64B2B22022FAD12E /* 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>"; }; C0D6DDD20DAAEA10C6999BD6 /* 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>"; };
C76E4DA09BCDA64E6AB1EC44 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; CAD4EDE547AF420693D76574 /* 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; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */ /* End PBXFileReference section */
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0DB59E54C0AC880864FE2DED /* libPods-example.a in Frameworks */, 5F4A3CC64B2ECE3AC87E1244 /* libPods-example.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
C76E4DA09BCDA64E6AB1EC44 /* libPods-example.a */, CAD4EDE547AF420693D76574 /* libPods-example.a */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -92,8 +92,8 @@ ...@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = { BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8A486A6AB07608F834FB5B89 /* Pods-example.debug.xcconfig */, C0D6DDD20DAAEA10C6999BD6 /* Pods-example.debug.xcconfig */,
917B7F7F64B2B22022FAD12E /* Pods-example.release.xcconfig */, BF80FACDA71FF077CAE23907 /* Pods-example.release.xcconfig */,
); );
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -105,13 +105,13 @@ ...@@ -105,13 +105,13 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = ( buildPhases = (
D1A8819C404F13A442A1FDE8 /* [CP] Check Pods Manifest.lock */, 19EDDD902D7748F2DDB86B4D /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */, 13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */, 13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
4B89C01D6158CC79657F83B1 /* [CP] Embed Pods Frameworks */, B56BFE2E5078044A567DF3A0 /* [CP] Embed Pods Frameworks */,
91AD97B7843DEC6888B2EDE6 /* [CP] Copy Pods Resources */, 788C036C201D78CD57E01EDA /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
...@@ -183,24 +183,29 @@ ...@@ -183,24 +183,29 @@
shellPath = /bin/sh; 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"; 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";
}; };
4B89C01D6158CC79657F83B1 /* [CP] Embed Pods Frameworks */ = { 19EDDD902D7748F2DDB86B4D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
name = "[CP] Embed Pods Frameworks"; inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist", );
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
91AD97B7843DEC6888B2EDE6 /* [CP] Copy Pods Resources */ = { 788C036C201D78CD57E01EDA /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
...@@ -217,26 +222,21 @@ ...@@ -217,26 +222,21 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
D1A8819C404F13A442A1FDE8 /* [CP] Check Pods Manifest.lock */ = { B56BFE2E5078044A567DF3A0 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = ( name = "[CP] Embed Pods Frameworks";
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = ( outputFileListPaths = (
); "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist",
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
...@@ -255,7 +255,7 @@ ...@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = { 13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 8A486A6AB07608F834FB5B89 /* Pods-example.debug.xcconfig */; baseConfigurationReference = C0D6DDD20DAAEA10C6999BD6 /* Pods-example.debug.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
...@@ -283,7 +283,7 @@ ...@@ -283,7 +283,7 @@
}; };
13B07F951A680F5B00A75B9A /* Release */ = { 13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 917B7F7F64B2B22022FAD12E /* Pods-example.release.xcconfig */; baseConfigurationReference = BF80FACDA71FF077CAE23907 /* Pods-example.release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
......
...@@ -8,32 +8,32 @@ ...@@ -8,32 +8,32 @@
<key>BinaryPath</key> <key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string> <string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key> <key>LibraryIdentifier</key>
<string>ios-arm64</string> <string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key> <key>LibraryPath</key>
<string>QmsPluginFramework.framework</string> <string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key> <key>SupportedArchitectures</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
<string>x86_64</string>
</array> </array>
<key>SupportedPlatform</key> <key>SupportedPlatform</key>
<string>ios</string> <string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict> </dict>
<dict> <dict>
<key>BinaryPath</key> <key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string> <string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key> <key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string> <string>ios-arm64</string>
<key>LibraryPath</key> <key>LibraryPath</key>
<string>QmsPluginFramework.framework</string> <string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key> <key>SupportedArchitectures</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
<string>x86_64</string>
</array> </array>
<key>SupportedPlatform</key> <key>SupportedPlatform</key>
<string>ios</string> <string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict> </dict>
</array> </array>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
@interface AppointmentAPIClient : NSObject @interface AppointmentAPIClient : NSObject
+ (void)fetchAppointment:(void (^)(NSDictionary *data, NSError *error))completion; + (void)fetchAppointment:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)fetchGeneralInfo:(void (^)(NSDictionary *data, NSError *error))completion;
@end @end
...@@ -69,4 +69,68 @@ ...@@ -69,4 +69,68 @@
[task resume]; [task resume];
} }
+ (void)fetchGeneralInfo:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/getUnitInfo?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
// ✅ JSON headers
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// ✅ Build JSON body
NSDictionary *jsonBody = @{
@"data": @{
@"os": @"AND",
@"drawing_plan_id": @"135"
}
};
// ✅ Convert dictionary → NSData
NSError *jsonError;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
if (jsonError) {
NSLog(@"❌ JSON Serialization Error: %@", jsonError);
if (completion) completion(nil, jsonError);
return;
}
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Network Error: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(nil, error);
});
return;
}
NSError *jsonParseError;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];
if (jsonParseError) {
NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
} else {
NSLog(@"✅ General Info API Response:\n%@", jsonResponse);
}
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(jsonResponse, jsonParseError);
});
}];
[task resume];
}
@end @end
// AppointmentTimeSlotViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AppointmentTimeSlotViewController : UIViewController
// Set this before pushing/presenting the VC:
@property (nonatomic, strong) NSDictionary *appointmentInfo;
// Optional callback for when user completes selection
@property (nonatomic, copy) void (^didSelectAppointment)(NSDictionary *appointmentInfo);
@property (nonatomic, strong) NSString *projectId;
@property (nonatomic, strong) NSString *projectName;
@property (nonatomic, strong) NSString *drawingPlanId;
@property (nonatomic, strong) NSString *unitName;
@property (nonatomic, strong) NSString *appointmentType;
@property (nonatomic, strong) NSArray *timeSlot;
@property (nonatomic, strong) NSString *termsAndCondition;
@property (nonatomic, strong) NSArray *representativeList;
@end
NS_ASSUME_NONNULL_END
// AppointmentTimeSlotViewController.mm
#import "AppointmentTimeSlotViewController.h"
@interface AppointmentTimeSlotViewController ()
// Header
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *cancelButton;
// Labels
@property (nonatomic, strong) UILabel *unitLabel;
@property (nonatomic, strong) UILabel *unitLabelName;
@property (nonatomic, strong) UILabel *stepLabel;
// Content
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIStackView *contentStack;
// Time slots
@property (nonatomic, strong) NSArray<NSDictionary *> *aryTimeSlot;
@property (nonatomic, strong) NSString *selectedSlotID;
@property (nonatomic, strong) NSString *selectedStartDate;
@property (nonatomic, strong) NSString *selectedStartTime;
// Footer
@property (nonatomic, strong) UIView *footerView;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UIButton *nextButton;
// Loading
@property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
@end
@implementation AppointmentTimeSlotViewController
#pragma mark - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
NSLog(@"📦 Data received by appointment:"
"\n projectId: %@"
"\n projectName: %@"
"\n drawingPlanId: %@"
"\n unitName: %@"
"\n appointmentType: %@"
"\n timeSlot: %@"
"\n termsAndCondition: %@"
"\n representativeList: %@",
self.projectId,
self.projectName,
self.drawingPlanId,
self.unitName,
self.appointmentType,
self.timeSlot,
self.termsAndCondition,
self.representativeList);
// Initialize defaults
self.aryTimeSlot = @[];
self.selectedSlotID = @"";
self.selectedStartDate = @"";
self.selectedStartTime = @"";
[self setupHeader];
[self setupScrollViewAndContent];
[self setupFooter];
[self setupLoadingIndicator];
// If appointmentInfo already provided, consume it
[self loadAppointmentInfoIfAvailable];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Optionally track screen view here
}
#pragma mark - Setup UI
- (void)setupHeader {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
self.headerBar = [[UIView alloc] init];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
self.headerBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // theme
[self.view addSubview:self.headerBar];
[NSLayoutConstraint activateConstraints:@[
[self.headerBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.headerBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerBar.heightAnchor constraintEqualToConstant:48]
]];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.titleLabel.text = @"Make Appointment";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
[self.headerBar addSubview:self.titleLabel];
self.cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.cancelButton setImage:[UIImage systemImageNamed:@"xmark"] forState:UIControlStateNormal];
self.cancelButton.tintColor = UIColor.whiteColor;
[self.cancelButton addTarget:self action:@selector(cancelPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.headerBar addSubview:self.cancelButton];
[NSLayoutConstraint activateConstraints:@[
[self.cancelButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.cancelButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.cancelButton.widthAnchor constraintEqualToConstant:36],
[self.cancelButton.heightAnchor constraintEqualToConstant:36],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor]
]];
}
- (void)setupScrollViewAndContent {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
[NSLayoutConstraint activateConstraints:@[
[self.scrollView.topAnchor constraintEqualToAnchor:self.headerBar.bottomAnchor],
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-80] // space for footer
]];
UIView *container = [[UIView alloc] init];
container.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:container];
[NSLayoutConstraint activateConstraints:@[
[container.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor],
[container.leadingAnchor constraintEqualToAnchor:self.scrollView.leadingAnchor],
[container.trailingAnchor constraintEqualToAnchor:self.scrollView.trailingAnchor],
[container.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor],
[container.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor],
]];
// Main vertical stack
self.contentStack = [[UIStackView alloc] init];
self.contentStack.translatesAutoresizingMaskIntoConstraints = NO;
self.contentStack.axis = UILayoutConstraintAxisVertical;
self.contentStack.spacing = 12;
[container addSubview:self.contentStack];
[NSLayoutConstraint activateConstraints:@[
[self.contentStack.topAnchor constraintEqualToAnchor:container.topAnchor constant:16],
[self.contentStack.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:16],
[self.contentStack.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-16],
[self.contentStack.bottomAnchor constraintLessThanOrEqualToAnchor:container.bottomAnchor constant:-16]
]];
// Unit label
self.unitLabel = [[UILabel alloc] init];
self.unitLabel.text = @"For Unit No.:";
self.unitLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightThin];
self.unitLabel.textColor = [UIColor grayColor];
self.unitLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.unitLabel];
self.unitLabelName = [[UILabel alloc] init];
self.unitLabelName.text = [NSString stringWithFormat:@"%@, %@", self.projectName, self.projectId];
self.unitLabelName.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
self.unitLabelName.textColor = [UIColor blackColor];
self.unitLabelName.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.unitLabelName];
self.stepLabel = [[UILabel alloc] init];
self.stepLabel.text = @"Step 2/3: Select your preferred time";
self.stepLabel.font = [UIFont systemFontOfSize:14];
self.stepLabel.textColor = [UIColor blackColor];
self.stepLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.stepLabel.textAlignment = NSTextAlignmentCenter;
self.stepLabel.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
self.stepLabel.layer.cornerRadius = 6.0;
self.stepLabel.clipsToBounds = YES;
[self.view addSubview:self.stepLabel];
[NSLayoutConstraint activateConstraints:@[
[self.unitLabel.topAnchor constraintEqualToAnchor:self.headerBar.bottomAnchor constant:16],
[self.unitLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.unitLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.unitLabelName.topAnchor constraintEqualToAnchor:self.unitLabel.bottomAnchor constant:8],
[self.unitLabelName.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.unitLabelName.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.stepLabel.topAnchor constraintEqualToAnchor:self.unitLabelName.bottomAnchor constant:16],
[self.stepLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.stepLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.stepLabel.heightAnchor constraintEqualToConstant:30]
]];
}
- (void)setupFooter {
self.footerView = [[UIView alloc] init];
self.footerView.translatesAutoresizingMaskIntoConstraints = NO;
self.footerView.backgroundColor = UIColor.whiteColor;
self.footerView.layer.shadowOpacity = 0.1;
self.footerView.layer.shadowOffset = CGSizeMake(0, -1);
[self.view addSubview:self.footerView];
[NSLayoutConstraint activateConstraints:@[
[self.footerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.footerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.footerView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.footerView.heightAnchor constraintEqualToConstant:80]
]];
// Back button
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.backButton setTitle:@"Back" forState:UIControlStateNormal];
self.backButton.layer.cornerRadius = 20;
self.backButton.layer.borderWidth = 1;
self.backButton.layer.borderColor = [UIColor colorWithRed:0.055 green:0.451 blue:0.573 alpha:1.0].CGColor;
[self.backButton setTitleColor:[UIColor colorWithRed:0.055 green:0.451 blue:0.573 alpha:1.0] forState:UIControlStateNormal];
[self.backButton addTarget:self action:@selector(backTapped:) forControlEvents:UIControlEventTouchUpInside];
// Next button
self.nextButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.nextButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.nextButton setTitle:@"Next" forState:UIControlStateNormal];
self.nextButton.layer.cornerRadius = 20;
self.nextButton.backgroundColor = [UIColor colorWithRed:0.055 green:0.451 blue:0.573 alpha:1.0];
[self.nextButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[self.nextButton addTarget:self action:@selector(nextTapped:) forControlEvents:UIControlEventTouchUpInside];
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[self.backButton, self.nextButton]];
stack.translatesAutoresizingMaskIntoConstraints = NO;
stack.axis = UILayoutConstraintAxisHorizontal;
stack.spacing = 12;
stack.distribution = UIStackViewDistributionFillEqually;
[self.footerView addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[stack.topAnchor constraintEqualToAnchor:self.footerView.topAnchor constant:12],
[stack.leadingAnchor constraintEqualToAnchor:self.footerView.leadingAnchor constant:16],
[stack.trailingAnchor constraintEqualToAnchor:self.footerView.trailingAnchor constant:-16],
[stack.bottomAnchor constraintEqualToAnchor:self.footerView.bottomAnchor constant:-12]
]];
}
- (void)setupLoadingIndicator {
self.loadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
self.loadingIndicator.translatesAutoresizingMaskIntoConstraints = NO;
self.loadingIndicator.hidesWhenStopped = YES;
[self.view addSubview:self.loadingIndicator];
[NSLayoutConstraint activateConstraints:@[
[self.loadingIndicator.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.loadingIndicator.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor]
]];
}
#pragma mark - Data handling
- (void)loadAppointmentInfoIfAvailable {
if (self.appointmentInfo && [self.appointmentInfo isKindOfClass:[NSDictionary class]]) {
// Populate UI and state from appointmentInfo
NSArray *timeSlots = self.appointmentInfo[@"time_slot"];
if ([timeSlots isKindOfClass:[NSArray class]]) {
self.aryTimeSlot = timeSlots;
} else {
self.aryTimeSlot = @[];
}
// Build time slot UI now that we have data
[self rebuildTimeSlotViews];
}
}
#pragma mark - Time Slot UI (2-column grid)
- (void)rebuildTimeSlotViews {
// First remove any previously added time-slot views from contentStack (after the step container)
// Find index of step container in contentStack then remove everything after it, or simply remove any arranged subviews that are not the first 3
// We'll remove any arranged subviews after the step container to keep labels intact.
while (self.contentStack.arrangedSubviews.count > 3) {
UIView *v = self.contentStack.arrangedSubviews.lastObject;
[self.contentStack removeArrangedSubview:v];
[v removeFromSuperview];
}
// Create container for time slots
UIView *slotsContainer = [[UIView alloc] init];
slotsContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentStack addArrangedSubview:slotsContainer];
// Build rows: two items per row
NSInteger count = self.aryTimeSlot.count;
for (NSInteger i = 0; i < count; i += 2) {
UIView *row = [[UIView alloc] init];
row.translatesAutoresizingMaskIntoConstraints = NO;
row.backgroundColor = UIColor.clearColor;
// Left slot
NSDictionary *left = self.aryTimeSlot[i];
UIButton *leftBtn = [self createTimeSlotButtonForIndex:i slot:left];
leftBtn.translatesAutoresizingMaskIntoConstraints = NO;
[row addSubview:leftBtn];
// Right slot (may not exist)
UIButton *rightBtn = nil;
if (i + 1 < count) {
NSDictionary *right = self.aryTimeSlot[i+1];
rightBtn = [self createTimeSlotButtonForIndex:i+1 slot:right];
rightBtn.translatesAutoresizingMaskIntoConstraints = NO;
[row addSubview:rightBtn];
}
[slotsContainer addSubview:row];
// Layout row children
CGFloat padding = 0;
[NSLayoutConstraint activateConstraints:@[
[row.leadingAnchor constraintEqualToAnchor:slotsContainer.leadingAnchor],
[row.trailingAnchor constraintEqualToAnchor:slotsContainer.trailingAnchor],
// height fixed same as RN (40)
[row.heightAnchor constraintEqualToConstant:40],
]];
[NSLayoutConstraint activateConstraints:@[
[leftBtn.leadingAnchor constraintEqualToAnchor:row.leadingAnchor constant:0],
[leftBtn.topAnchor constraintEqualToAnchor:row.topAnchor constant:0],
[leftBtn.bottomAnchor constraintEqualToAnchor:row.bottomAnchor constant:0],
[leftBtn.widthAnchor constraintEqualToAnchor:row.widthAnchor multiplier:0.5 constant:-10]
]];
if (rightBtn) {
[NSLayoutConstraint activateConstraints:@[
[rightBtn.trailingAnchor constraintEqualToAnchor:row.trailingAnchor constant:0],
[rightBtn.topAnchor constraintEqualToAnchor:row.topAnchor constant:0],
[rightBtn.bottomAnchor constraintEqualToAnchor:row.bottomAnchor constant:0],
[rightBtn.leadingAnchor constraintEqualToAnchor:leftBtn.trailingAnchor constant:20],
[rightBtn.widthAnchor constraintEqualToAnchor:leftBtn.widthAnchor]
]];
} else {
// if right missing, fill space with empty view to keep alignment
UIView *filler = [[UIView alloc] init];
filler.translatesAutoresizingMaskIntoConstraints = NO;
[row addSubview:filler];
[NSLayoutConstraint activateConstraints:@[
[filler.leadingAnchor constraintEqualToAnchor:leftBtn.trailingAnchor constant:20],
[filler.trailingAnchor constraintEqualToAnchor:row.trailingAnchor constant:0],
[filler.topAnchor constraintEqualToAnchor:row.topAnchor constant:0],
[filler.bottomAnchor constraintEqualToAnchor:row.bottomAnchor constant:0]
]];
}
// Add spacing below each row
UIView *spacer = [[UIView alloc] init];
spacer.translatesAutoresizingMaskIntoConstraints = NO;
spacer.backgroundColor = UIColor.clearColor;
[self.contentStack addArrangedSubview:row];
[self.contentStack addArrangedSubview:spacer];
[NSLayoutConstraint activateConstraints:@[
[spacer.heightAnchor constraintEqualToConstant:8]
]];
}
}
- (UIButton *)createTimeSlotButtonForIndex:(NSInteger)index slot:(NSDictionary *)slot {
NSString *startTime = slot[@"start_time"] ?: @"";
NSString *slotID = [slot[@"slot_id"] ?: slot[@"id"] stringValue] ?: @"";
NSString *startDate = slot[@"start_date"] ?: @"";
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.tag = index; // identify which slot
btn.translatesAutoresizingMaskIntoConstraints = NO;
btn.layer.cornerRadius = 20;
btn.layer.borderWidth = 0.5;
btn.layer.borderColor = [UIColor colorWithWhite:0.63 alpha:1.0].CGColor;
btn.backgroundColor = UIColor.clearColor;
[btn setTitle:[self convertTimeString:startTime] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor colorWithWhite:0.14 alpha:1.0] forState:UIControlStateNormal];
btn.titleLabel.font = [UIFont systemFontOfSize:14];
// store identifying info using associated objects? easiest is set accessibilityValue
btn.accessibilityIdentifier = slotID;
btn.accessibilityValue = startDate;
[btn addTarget:self action:@selector(timeSlotTapped:) forControlEvents:UIControlEventTouchUpInside];
// If already selected, style it
if (self.selectedSlotID && [self.selectedSlotID isEqualToString:slotID]) {
btn.layer.borderColor = [UIColor colorWithRed:0.055 green:0.451 blue:0.573 alpha:1.0].CGColor;
btn.backgroundColor = [UIColor colorWithRed:0.863 green:0.969 blue:1.0 alpha:1.0]; // light blue
}
return btn;
}
#pragma mark - Actions
- (void)cancelPressed:(id)sender {
UIAlertController *a = [UIAlertController alertControllerWithTitle:@"Cancel"
message:@"Are you sure you want to cancel?"
preferredStyle:UIAlertControllerStyleAlert];
[a addAction:[UIAlertAction actionWithTitle:@"No" style:UIAlertActionStyleCancel handler:nil]];
[a addAction:[UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
// pop to dashboard or dismiss - replicate RN behavior
[self.navigationController popToRootViewControllerAnimated:YES];
}]];
[self presentViewController:a animated:YES completion:nil];
}
- (void)backTapped:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)nextTapped:(id)sender {
if (self.selectedSlotID.length == 0 || self.selectedStartDate.length == 0 || self.selectedStartTime.length == 0) {
UIAlertController *a = [UIAlertController alertControllerWithTitle:@"Alert" message:@"Please select a time slot to continue." preferredStyle:UIAlertControllerStyleAlert];
[a addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:a animated:YES completion:nil];
return;
}
NSMutableDictionary *obj = [self.appointmentInfo mutableCopy] ?: [NSMutableDictionary dictionary];
obj[@"slot_id"] = self.selectedSlotID;
obj[@"start_date"] = self.selectedStartDate;
obj[@"start_time"] = self.selectedStartTime;
if (self.didSelectAppointment) {
self.didSelectAppointment([obj copy]);
} else {
// push AppointmentConfirmationViewController (assumes it exists)
// You must replace the below lines with your app's confirmation screen push
UIViewController *conf = [NSClassFromString(@"AppointmentConfirmationViewController") new];
if (conf) {
if ([conf respondsToSelector:@selector(setAppointmentInfo:)]) {
// prefer setter if exists
[conf setValue:[obj copy] forKey:@"appointmentInfo"];
}
[self.navigationController pushViewController:conf animated:YES];
} else {
// fallback: just log
NSLog(@"Next tapped - appointment object: %@", obj);
}
}
}
- (void)timeSlotTapped:(UIButton *)sender {
NSString *slotID = sender.accessibilityIdentifier ?: @"";
NSString *startDate = sender.accessibilityValue ?: @"";
// find slot object to get start_time
NSInteger idx = sender.tag;
NSDictionary *slot = nil;
if (idx >= 0 && idx < self.aryTimeSlot.count) slot = self.aryTimeSlot[idx];
NSString *startTime = slot[@"start_time"] ?: @"";
self.selectedSlotID = slotID;
self.selectedStartDate = startDate;
self.selectedStartTime = startTime;
// Refresh UI: rebuild the slot views to reapply selected state
[self rebuildTimeSlotViews];
}
#pragma mark - Helpers
- (NSString *)convertTimeString:(NSString *)timeStr {
// expects "HH:mm:ss" return "HH:mm"
if (!timeStr || timeStr.length == 0) return @"";
NSDateFormatter *in = [[NSDateFormatter alloc] init];
in.dateFormat = @"HH:mm:ss";
NSDate *d = [in dateFromString:timeStr];
if (!d) {
// try HH:mm
in.dateFormat = @"HH:mm";
d = [in dateFromString:timeStr];
}
NSDateFormatter *out = [[NSDateFormatter alloc] init];
out.dateFormat = @"HH:mm";
if (d) return [out stringFromDate:d];
return timeStr;
}
#pragma mark - Loading
- (void)setLoading:(BOOL)loading {
dispatch_async(dispatch_get_main_queue(), ^{
if (loading) {
[self.loadingIndicator startAnimating];
} else {
[self.loadingIndicator stopAnimating];
}
});
}
@end
...@@ -9,6 +9,13 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -9,6 +9,13 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong) NSString *projectName; @property (nonatomic, strong) NSString *projectName;
@property (nonatomic, strong) NSString *projectId; @property (nonatomic, strong) NSString *projectId;
@property (nonatomic, strong) NSArray *allSlots; // Equivalent to rawSlots
@property (nonatomic, strong) NSMutableArray *aryTimeSlot; // Equivalent to aryTimeSlotTemp
@property (nonatomic, strong) NSString *selectedDateString;
@property (nonatomic, strong) NSDictionary *markedDates;
@property (nonatomic, strong) UICollectionView *timeSlotCollectionView;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#import "AppointmentAPIClient.h" #import "AppointmentAPIClient.h"
#import <WebKit/WebKit.h> #import <WebKit/WebKit.h>
#import "CalendarView.h" #import "CalendarView.h"
#import "NSDate+CalendarHelpers.h"
#import "AppointmentTimeSlotViewController.h"
@interface AppointmentViewController () <WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate, CalendarViewDelegate> @interface AppointmentViewController () <WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate, CalendarViewDelegate>
...@@ -16,6 +18,8 @@ ...@@ -16,6 +18,8 @@
@property (nonatomic, strong) UIScrollView *scrollView; @property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView; @property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) NSArray *representativeList;
// Terms & Conditions // Terms & Conditions
@property (nonatomic, strong) UIView *termsOverlay; @property (nonatomic, strong) UIView *termsOverlay;
@property (nonatomic, strong) UITextView *termsTextView; @property (nonatomic, strong) UITextView *termsTextView;
...@@ -25,11 +29,19 @@ ...@@ -25,11 +29,19 @@
@property (nonatomic, strong) UIView *termsContainer; @property (nonatomic, strong) UIView *termsContainer;
@property (nonatomic, strong) NSDictionary *appointmentData; @property (nonatomic, strong) NSDictionary *appointmentData;
@property (nonatomic, strong) WKWebView *termsWebView; @property (nonatomic, strong) WKWebView *termsWebView;
@property (nonatomic, strong) NSString *termsAndCondition;
@property (nonatomic, strong) NSString *appointmentType;
// Labels
@property (nonatomic, strong) UILabel *unitLabel;
@property (nonatomic, strong) UILabel *unitName;
@property (nonatomic, strong) UILabel *stepLabel;
// Calendar // Calendar
@property (nonatomic, strong) UIView *calendarContainer; @property (nonatomic, strong) UIView *calendarContainer;
@property (nonatomic, strong) UIStackView *legendStack; @property (nonatomic, strong) UIStackView *legendStack;
@property (nonatomic, strong) CalendarView *calendarView; @property (nonatomic, strong) CalendarView *calendarView;
@property (nonatomic, strong, readwrite) UICollectionView *collectionView;
// Footer buttons // Footer buttons
@property (nonatomic, strong) UIView *footerView; @property (nonatomic, strong) UIView *footerView;
...@@ -51,13 +63,13 @@ ...@@ -51,13 +63,13 @@
[self setupUI]; [self setupUI];
[self setupTermsOverlay]; [self setupTermsOverlay];
[self fetchAppointment]; [self fetchAppointment];
[self fetchGeneralInfo];
} }
#pragma mark - Setup UI #pragma mark - Setup UI
- (void)setupUI { - (void)setupUI {
[self setupHeader]; [self setupHeader];
[self setupScrollView]; [self setupLabels];
[self setupCalendarSection]; [self setupCalendarSection];
[self setupFooter]; [self setupFooter];
[self setupTutorialOverlay]; [self setupTutorialOverlay];
...@@ -179,97 +191,117 @@ ...@@ -179,97 +191,117 @@
} }
#pragma mark - Scroll View #pragma mark - Labels
- (void)setupScrollView { - (void)setupLabels {
self.scrollView = [[UIScrollView alloc] init]; UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView]; self.unitLabel = [[UILabel alloc] init];
self.unitLabel.text = @"For Unit No.:";
[NSLayoutConstraint activateConstraints:@[ self.unitLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightThin];
[self.scrollView.topAnchor constraintEqualToAnchor:self.headerBar.bottomAnchor], self.unitLabel.textColor = [UIColor grayColor];
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], self.unitLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], [self.view addSubview:self.unitLabel];
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-100]
]];
self.contentView = [[UIView alloc] init];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:self.contentView];
[NSLayoutConstraint activateConstraints:@[ self.unitName = [[UILabel alloc] init];
[self.contentView.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor], self.unitName.text = [NSString stringWithFormat:@"%@, %@", self.projectName, self.projectCode];
[self.contentView.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor], self.unitName.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[self.contentView.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor] self.unitName.textColor = [UIColor blackColor];
]]; self.unitName.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.unitName];
self.stepLabel = [[UILabel alloc] init];
self.stepLabel.text = @"Step 1/3: Select your preferred date";
self.stepLabel.font = [UIFont systemFontOfSize:14];
self.stepLabel.textColor = [UIColor blackColor];
self.stepLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.stepLabel.textAlignment = NSTextAlignmentCenter;
self.stepLabel.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
self.stepLabel.layer.cornerRadius = 6.0;
self.stepLabel.clipsToBounds = YES;
[self.view addSubview:self.stepLabel];
[NSLayoutConstraint activateConstraints:@[
[self.unitLabel.topAnchor constraintEqualToAnchor:self.headerBar.bottomAnchor constant:16],
[self.unitLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.unitLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.unitName.topAnchor constraintEqualToAnchor:self.unitLabel.bottomAnchor constant:8],
[self.unitName.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.unitName.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.stepLabel.topAnchor constraintEqualToAnchor:self.unitName.bottomAnchor constant:16],
[self.stepLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.stepLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.stepLabel.heightAnchor constraintEqualToConstant:30]
]];
} }
#pragma mark - Calendar Section #pragma mark - Calendar Section
- (void)setupCalendarSection { - (void)setupCalendarSection {
self.calendarContainer = [[UIView alloc] init]; self.calendarContainer = [[UIView alloc] init];
self.calendarContainer.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0]; self.calendarContainer.backgroundColor = [UIColor whiteColor];
self.calendarContainer.layer.cornerRadius = 12.0; self.calendarContainer.layer.cornerRadius = 12.0;
self.calendarContainer.translatesAutoresizingMaskIntoConstraints = NO; self.calendarContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.calendarContainer]; [self.view addSubview:self.calendarContainer];
self.calendarView = [[CalendarView alloc] initWithFrame:CGRectZero]; self.calendarView = [[CalendarView alloc] initWithFrame:CGRectZero];
self.calendarView.translatesAutoresizingMaskIntoConstraints = NO; self.calendarView.translatesAutoresizingMaskIntoConstraints = NO;
self.calendarView.delegate = self; self.calendarView.delegate = self;
[self.calendarContainer addSubview:self.calendarView]; [self.calendarContainer addSubview:self.calendarView];
// Legend stack // Legend stack
self.legendStack = [[UIStackView alloc] init]; self.legendStack = [[UIStackView alloc] init];
self.legendStack.axis = UILayoutConstraintAxisHorizontal; self.legendStack.axis = UILayoutConstraintAxisHorizontal;
self.legendStack.spacing = 16.0; self.legendStack.spacing = 16.0;
self.legendStack.alignment = UIStackViewAlignmentCenter; self.legendStack.alignment = UIStackViewAlignmentCenter;
self.legendStack.distribution = UIStackViewDistributionEqualSpacing; self.legendStack.distribution = UIStackViewDistributionEqualSpacing;
self.legendStack.translatesAutoresizingMaskIntoConstraints = NO; self.legendStack.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.legendStack]; [self.view addSubview:self.legendStack];
NSArray *legendData = @[ NSArray *legendData = @[
@{@"title": @"Available", @"color": UIColor.systemGreenColor}, @{@"title": @"Available", @"color": UIColor.systemGreenColor},
@{@"title": @"Limited", @"color": UIColor.systemOrangeColor}, @{@"title": @"Limited", @"color": UIColor.systemOrangeColor},
@{@"title": @"Unavailable", @"color": UIColor.systemRedColor} @{@"title": @"Unavailable", @"color": UIColor.systemRedColor}
]; ];
for (NSDictionary *item in legendData) { for (NSDictionary *item in legendData) {
UIView *dot = [[UIView alloc] init]; UIView *dot = [[UIView alloc] init];
dot.backgroundColor = item[@"color"]; dot.backgroundColor = item[@"color"];
dot.layer.cornerRadius = 6; dot.layer.cornerRadius = 6;
dot.translatesAutoresizingMaskIntoConstraints = NO; dot.translatesAutoresizingMaskIntoConstraints = NO;
[dot.heightAnchor constraintEqualToConstant:12].active = YES; [dot.heightAnchor constraintEqualToConstant:12].active = YES;
[dot.widthAnchor constraintEqualToConstant:12].active = YES; [dot.widthAnchor constraintEqualToConstant:12].active = YES;
UILabel *label = [[UILabel alloc] init]; UILabel *label = [[UILabel alloc] init];
label.text = item[@"title"]; label.text = item[@"title"];
label.font = [UIFont systemFontOfSize:13]; label.font = [UIFont systemFontOfSize:13];
UIStackView *pair = [[UIStackView alloc] initWithArrangedSubviews:@[dot, label]]; UIStackView *pair = [[UIStackView alloc] initWithArrangedSubviews:@[dot, label]];
pair.axis = UILayoutConstraintAxisHorizontal; pair.axis = UILayoutConstraintAxisHorizontal;
pair.spacing = 4.0; pair.spacing = 4.0;
[self.legendStack addArrangedSubview:pair]; [self.legendStack addArrangedSubview:pair];
} }
[NSLayoutConstraint activateConstraints:@[
[self.calendarContainer.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:20],
[self.calendarContainer.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.calendarContainer.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[self.calendarView.topAnchor constraintEqualToAnchor:self.calendarContainer.topAnchor constant:12],
[self.calendarView.leadingAnchor constraintEqualToAnchor:self.calendarContainer.leadingAnchor constant:12],
[self.calendarView.trailingAnchor constraintEqualToAnchor:self.calendarContainer.trailingAnchor constant:-12],
[self.calendarView.bottomAnchor constraintEqualToAnchor:self.calendarContainer.bottomAnchor constant:-12],
[self.legendStack.topAnchor constraintEqualToAnchor:self.calendarContainer.bottomAnchor constant:16],
[self.legendStack.centerXAnchor constraintEqualToAnchor:self.contentView.centerXAnchor]
]];
CGFloat w = UIScreen.mainScreen.bounds.size.width - 16 - 16 - 12 - 12; // container + inner padding
CGFloat cell = floor(w / 7.0);
CGFloat rows = 6; // max rows calendar ever needs
CGFloat calendarHeight = 40 + 28 + (cell * 0.9 + 12) * rows + 12 + 15;
[self.calendarView.heightAnchor constraintEqualToConstant:calendarHeight].active = YES; CGFloat w = UIScreen.mainScreen.bounds.size.width - 16 - 16 - 12 - 12; // container + inner padding
CGFloat cell = floor(w / 7.0);
CGFloat rows = 6; // max rows calendar ever needs
CGFloat calendarHeight = 40 + 28 + (cell * 0.9 + 12) * rows + 12 + 20;
[NSLayoutConstraint activateConstraints:@[
[self.calendarContainer.topAnchor constraintEqualToAnchor:self.stepLabel.bottomAnchor constant:16],
[self.calendarContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.calendarContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.calendarContainer.heightAnchor constraintEqualToConstant:calendarHeight],
[self.calendarView.topAnchor constraintEqualToAnchor:self.calendarContainer.topAnchor constant:12],
[self.calendarView.leadingAnchor constraintEqualToAnchor:self.calendarContainer.leadingAnchor constant:12],
[self.calendarView.trailingAnchor constraintEqualToAnchor:self.calendarContainer.trailingAnchor constant:-12],
[self.calendarView.bottomAnchor constraintEqualToAnchor:self.calendarContainer.bottomAnchor constant:-12],
[self.legendStack.topAnchor constraintEqualToAnchor:self.calendarContainer.bottomAnchor constant:16],
[self.legendStack.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
]];
} }
#pragma mark - Footer Buttons #pragma mark - Footer Buttons
...@@ -279,6 +311,7 @@ ...@@ -279,6 +311,7 @@
self.footerView.layer.shadowOpacity = 0.1; self.footerView.layer.shadowOpacity = 0.1;
self.footerView.layer.shadowOffset = CGSizeMake(0, -1); self.footerView.layer.shadowOffset = CGSizeMake(0, -1);
self.footerView.translatesAutoresizingMaskIntoConstraints = NO; self.footerView.translatesAutoresizingMaskIntoConstraints = NO;
self.footerView.userInteractionEnabled = YES;
[self.view addSubview:self.footerView]; [self.view addSubview:self.footerView];
[NSLayoutConstraint activateConstraints:@[ [NSLayoutConstraint activateConstraints:@[
...@@ -294,12 +327,18 @@ ...@@ -294,12 +327,18 @@
self.backFooterButton.layer.borderWidth = 1.0; self.backFooterButton.layer.borderWidth = 1.0;
self.backFooterButton.layer.cornerRadius = 20.0; self.backFooterButton.layer.cornerRadius = 20.0;
[self.backFooterButton setTitleColor:[UIColor systemTealColor] forState:UIControlStateNormal]; [self.backFooterButton setTitleColor:[UIColor systemTealColor] forState:UIControlStateNormal];
[self.backFooterButton addTarget:self
action:@selector(footerBackTapped)
forControlEvents:UIControlEventTouchUpInside];
self.nextFooterButton = [UIButton buttonWithType:UIButtonTypeSystem]; self.nextFooterButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.nextFooterButton setTitle:@"Next" forState:UIControlStateNormal]; [self.nextFooterButton setTitle:@"Next" forState:UIControlStateNormal];
self.nextFooterButton.backgroundColor = [UIColor systemTealColor]; self.nextFooterButton.backgroundColor = [UIColor systemTealColor];
self.nextFooterButton.tintColor = UIColor.whiteColor; self.nextFooterButton.tintColor = UIColor.whiteColor;
self.nextFooterButton.layer.cornerRadius = 20.0; self.nextFooterButton.layer.cornerRadius = 20.0;
[self.nextFooterButton addTarget:self
action:@selector(footerNextTapped)
forControlEvents:UIControlEventTouchUpInside];
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[self.backFooterButton, self.nextFooterButton]]; UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[self.backFooterButton, self.nextFooterButton]];
stack.axis = UILayoutConstraintAxisHorizontal; stack.axis = UILayoutConstraintAxisHorizontal;
...@@ -362,48 +401,68 @@ ...@@ -362,48 +401,68 @@
- (void)fetchAppointment { - (void)fetchAppointment {
[AppointmentAPIClient fetchAppointment:^(NSDictionary *data, NSError *error) { [AppointmentAPIClient fetchAppointment:^(NSDictionary *data, NSError *error) {
if (error) return; if (error) return;
if (!data[@"Data"]) return;
NSDictionary *appointData = data;
NSString *text = [NSString stringWithFormat:@"%@", appointData]; NSDictionary *dataDict = data[@"Data"];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; self.appointmentData = dataDict;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"appointmentData.txt"];
NSString *plainText = [NSString stringWithFormat:@"%@", appointData]; // --- 1️⃣ Determine Appointment Type ---
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; self.appointmentType = @"joint_inspection";
NSLog(@"📁 appointmentData saved as plain text to: %@", filePath);
self.appointmentData = appointData[@"Data"];
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *jointInspection = self.appointmentData[@"joint_inspection"];
NSDictionary *handover = self.appointmentData[@"handover"];
NSString *termsHTML = jointInspection[@"terms"] ?: handover[@"terms"];
if (termsHTML.length == 0) { id handoverStatus = dataDict[@"handover_status"];
NSLog(@"⚠️ termsHTML is EMPTY"); id clearanceLetter = dataDict[@"clearance_letter"];
} else {
NSLog(@"✅ Loading terms HTML (%lu chars)", termsHTML.length); if (handoverStatus && ![handoverStatus isKindOfClass:[NSNull class]] &&
} clearanceLetter && ![clearanceLetter isKindOfClass:[NSNull class]]) {
if ([[NSString stringWithFormat:@"%@", clearanceLetter] isEqualToString:@"1"] &&
[[NSString stringWithFormat:@"%@", handoverStatus] isEqualToString:@"not handed over"]) {
self.appointmentType = @"handover";
}
}
// --- 2️⃣ Select Slot Data Based on Appointment Type ---
NSDictionary *selectedTypeDict = dataDict[self.appointmentType];
NSArray *slots = selectedTypeDict[@"slots"] ?: @[];
self.allSlots = slots; // store all raw slots
// --- 3️⃣ Load Terms HTML ---
NSString *termsHTML = selectedTypeDict[@"terms"] ?: @"";
NSString *styledHTML = [NSString stringWithFormat:
@"<html>"
"<head>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\">"
"<style>"
"body { font-size: 14px; font-family: -apple-system; line-height: 1.5; padding: 12px; background-color: #F9F9F9; }"
"p { margin-bottom: 12px; }"
"</style>"
"</head>"
"<body>%@</body>"
"</html>", termsHTML];
NSString *styledHTML = [NSString stringWithFormat: self.termsAndCondition = styledHTML;
@"<html>"
"<head>" dispatch_async(dispatch_get_main_queue(), ^{
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\">" [self.termsWebView loadHTMLString:styledHTML baseURL:nil];
"<style>" [self buildMarkedDatesFromSlots:slots];
"body {" [self.calendarView.collectionView reloadData];
"font-size: 14px;" });
"font-family: -apple-system;" }];
"line-height: 1.5;" }
"padding: 12px;"
"background-color: #F9F9F9;" -(void) fetchGeneralInfo {
"}" [AppointmentAPIClient fetchGeneralInfo:^(NSDictionary *data, NSError *error) {
"p { margin-bottom: 12px; }" if (error) return;
"</style>"
"</head>" NSDictionary *generalInfo = data[@"Data"];
"<body>%@</body>" id reps = generalInfo[@"representative"];
"</html>", termsHTML];
if ([reps isKindOfClass:[NSArray class]]) {
[self.termsWebView loadHTMLString:styledHTML baseURL:nil]; self.representativeList = (NSArray *)reps;
}); } else {
self.representativeList = @[]; // fallback to empty
}
}]; }];
} }
...@@ -450,13 +509,10 @@ ...@@ -450,13 +509,10 @@
} }
- (void)handleAgreeTapped { - (void)handleAgreeTapped {
self.calendarContainer.userInteractionEnabled = YES;
self.footerView.userInteractionEnabled = YES;
[UIView animateWithDuration:0.25 animations:^{ [UIView animateWithDuration:0.25 animations:^{
self.termsOverlay.alpha = 0.0; self.termsOverlay.alpha = 0.0;
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
self.termsOverlay.hidden = YES; self.termsOverlay.hidden = YES;
self.termsOverlay.alpha = 1.0;
}]; }];
} }
...@@ -471,11 +527,121 @@ ...@@ -471,11 +527,121 @@
} }
- (void)calendarView:(CalendarView *)calendar didSelectDate:(NSDate *)date { - (void)calendarView:(CalendarView *)calendar didSelectDate:(NSDate *)date {
NSLog(@"Selected date = %@", date); NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = @"yyyy-MM-dd";
NSString *selectedDate = [fmt stringFromDate:date];
self.selectedDateString = selectedDate;
NSMutableArray *filteredSlots = [NSMutableArray array];
for (NSDictionary *slot in self.allSlots) {
NSString *slotDate = slot[@"start_date"];
NSInteger remaining = [slot[@"slots"] integerValue];
if ([slotDate isEqualToString:selectedDate] && remaining > 0) {
NSMutableDictionary *timeObj = [NSMutableDictionary dictionary];
timeObj[@"slot_id"] = slot[@"id"];
timeObj[@"start_date"] = slot[@"start_date"];
timeObj[@"start_time"] = slot[@"start_time"];
[filteredSlots addObject:timeObj];
}
}
if (filteredSlots.count == 0) {
// Unselect the date visually AND logically
self.selectedDateString = @"";
self.calendarView.selectedDate = nil;
[self.calendarView.collectionView reloadData];
[self showNoSlotsAlert];
self.aryTimeSlot = [NSMutableArray array];
return;
}
// Sort slots by time
NSArray *sorted = [filteredSlots sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
return [a[@"start_time"] compare:b[@"start_time"]];
}];
self.aryTimeSlot = [sorted mutableCopy];
NSLog(@"Final aryTimeSlot = %@", self.aryTimeSlot);
} }
- (void)calendarViewMonthChanged:(NSDate *)monthFirstDay { - (void)calendarViewMonthChanged:(NSDate *)monthFirstDay {
NSLog(@"Month changed = %@", monthFirstDay); NSLog(@"Month changed = %@", monthFirstDay);
} }
- (void)showTermsOverlay {
self.termsOverlay.hidden = NO;
self.termsOverlay.alpha = 0.0;
[UIView animateWithDuration:0.25 animations:^{
self.termsOverlay.alpha = 1.0;
}];
}
-(void)footerBackTapped {
[self showTermsOverlay];
}
-(void)footerNextTapped {
if (!self.selectedDateString || self.selectedDateString.length == 0) {
// No date selected – show an alert
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Select Date"
message:@"Please select a date before proceeding."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
return; // stop execution
}
NSLog(@"Navigating to AppointmentTimeSlot");
AppointmentTimeSlotViewController *appointVC = [[AppointmentTimeSlotViewController alloc] init];
appointVC.projectId = self.projectCode;
appointVC.projectName = self.projectName;
appointVC.drawingPlanId = @"135";
appointVC.unitName = self.projectName;
appointVC.appointmentType = self.appointmentType;
appointVC.timeSlot = self.aryTimeSlot;
appointVC.termsAndCondition = self.termsAndCondition;
appointVC.representativeList = self.representativeList;
appointVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:appointVC animated:YES completion:nil];
}
- (void)buildMarkedDatesFromSlots:(NSArray *)slots {
NSMutableDictionary *marked = [NSMutableDictionary dictionary];
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = @"yyyy-MM-dd";
for (NSDictionary *slot in slots) {
NSString *dateStr = slot[@"start_date"];
NSInteger remaining = [slot[@"slots"] integerValue];
NSString *status = @"available";
if (remaining == 0) status = @"unavailable";
else if (remaining < 5) status = @"limited";
marked[dateStr] = @{ @"status": status };
}
self.markedDates = marked;
self.calendarView.markedDates = marked;
}
- (void)showNoSlotsAlert {
NSString *msg = [self.appointmentType isEqualToString:@"joint_inspection"]
? @"No available Joint Inspection slots for this date."
: @"No available Handover slots for this date.";
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Unavailable"
message:msg
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
@end @end
...@@ -24,6 +24,8 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -24,6 +24,8 @@ NS_ASSUME_NONNULL_BEGIN
// currently selected date // currently selected date
@property (nonatomic, strong, nullable) NSDate *selectedDate; @property (nonatomic, strong, nullable) NSDate *selectedDate;
@property (nonatomic, strong, readonly) UICollectionView *collectionView;
// set starting month (default = today) // set starting month (default = today)
- (void)setDisplayedMonth:(NSDate *)monthAnimated:(BOOL)animated; - (void)setDisplayedMonth:(NSDate *)monthAnimated:(BOOL)animated;
......
...@@ -191,11 +191,15 @@ static const NSInteger kColumns = 7; ...@@ -191,11 +191,15 @@ static const NSInteger kColumns = 7;
} }
#pragma mark - collection view datasource #pragma mark - collection view datasource
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
CalendarDayCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kDayCellId forIndexPath:indexPath]; cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CalendarDayCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kDayCellId
forIndexPath:indexPath];
NSDate *d = [self dateForIndexPathItem:indexPath.item]; NSDate *d = [self dateForIndexPathItem:indexPath.item];
BOOL inMonth = (d != nil); BOOL inMonth = (d != nil);
NSNumber *dayNumber = nil; NSNumber *dayNumber = nil;
if (d) { if (d) {
NSCalendar *cal = [NSCalendar currentCalendar]; NSCalendar *cal = [NSCalendar currentCalendar];
...@@ -203,17 +207,37 @@ static const NSInteger kColumns = 7; ...@@ -203,17 +207,37 @@ static const NSInteger kColumns = 7;
dayNumber = @(c.day); dayNumber = @(c.day);
} }
BOOL enabled = YES;
if (d && self.minDate) {
if ([d compare:self.minDate] == NSOrderedAscending) {
enabled = NO;
}
}
NSString *status = [self statusForDate:d]; NSString *status = [self statusForDate:d];
BOOL selected = (d && self.selectedDate && [d isSameDayAsDate:self.selectedDate]); BOOL selected = (d && self.selectedDate && [d isSameDayAsDate:self.selectedDate]);
// Configure day number / selection AFTER color & userInteraction are set
[cell configureWithDay:dayNumber
inMonth:inMonth
enabled:YES // always pass YES here; past/today handled above
status:status
selected:selected];
// Reset cell to defaults
cell.layer.borderWidth = 0;
cell.layer.borderColor = nil;
cell.dayLabel.textColor = [UIColor blackColor];
cell.userInteractionEnabled = YES;
NSDate *today = [NSDate date];
NSCalendar *cal = [NSCalendar currentCalendar];
[cell configureWithDay:dayNumber inMonth:inMonth enabled:enabled status:status selected:selected]; // Grey out past dates
if (d && [d compare:today] == NSOrderedAscending) {
cell.dayLabel.textColor = [UIColor lightGrayColor];
cell.userInteractionEnabled = NO;
}
// Highlight today (not selectable)
BOOL isToday = d && [cal isDate:d inSameDayAsDate:today];
if (isToday) {
cell.dayLabel.textColor = [UIColor systemBlueColor];
cell.userInteractionEnabled = NO;
}
return cell; return cell;
} }
...@@ -222,10 +246,11 @@ static const NSInteger kColumns = 7; ...@@ -222,10 +246,11 @@ static const NSInteger kColumns = 7;
NSDate *d = [self dateForIndexPathItem:indexPath.item]; NSDate *d = [self dateForIndexPathItem:indexPath.item];
if (!d) return; if (!d) return;
NSString *status = [self statusForDate:d];
if (self.minDate && [d compare:self.minDate] == NSOrderedAscending) return; if (self.minDate && [d compare:self.minDate] == NSOrderedAscending) return;
// allow selection only if not unavailable? (you can change) // allow selection only if not unavailable? (you can change)
NSString *status = [self statusForDate:d];
// if unavailable, ignore // if unavailable, ignore
if ([status isEqualToString:@"unavailable"]) { if ([status isEqualToString:@"unavailable"]) {
return; return;
......
...@@ -8,32 +8,32 @@ ...@@ -8,32 +8,32 @@
<key>BinaryPath</key> <key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string> <string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key> <key>LibraryIdentifier</key>
<string>ios-arm64</string> <string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key> <key>LibraryPath</key>
<string>QmsPluginFramework.framework</string> <string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key> <key>SupportedArchitectures</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
<string>x86_64</string>
</array> </array>
<key>SupportedPlatform</key> <key>SupportedPlatform</key>
<string>ios</string> <string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict> </dict>
<dict> <dict>
<key>BinaryPath</key> <key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string> <string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key> <key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string> <string>ios-arm64</string>
<key>LibraryPath</key> <key>LibraryPath</key>
<string>QmsPluginFramework.framework</string> <string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key> <key>SupportedArchitectures</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
<string>x86_64</string>
</array> </array>
<key>SupportedPlatform</key> <key>SupportedPlatform</key>
<string>ios</string> <string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict> </dict>
</array> </array>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment