Commit 1ce35c7d authored by Wei Han's avatar Wei Han

code update

parent 09397ebc
......@@ -9,9 +9,9 @@
/* Begin PBXBuildFile section */
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
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 */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
D458042D2F0D3FDEC170C143 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B322AC5237F8AE5669A0FABE /* libPods-example.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
......@@ -19,11 +19,11 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = "<group>"; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = example/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
38EC829E51DAE9347EEBB719 /* 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>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; 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>"; };
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>"; };
CAD4EDE547AF420693D76574 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B322AC5237F8AE5669A0FABE /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
DE57DCC5FB89A01CC4F15D08 /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
......@@ -32,7 +32,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5F4A3CC64B2ECE3AC87E1244 /* libPods-example.a in Frameworks */,
D458042D2F0D3FDEC170C143 /* libPods-example.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -55,7 +55,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
CAD4EDE547AF420693D76574 /* libPods-example.a */,
B322AC5237F8AE5669A0FABE /* libPods-example.a */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
C0D6DDD20DAAEA10C6999BD6 /* Pods-example.debug.xcconfig */,
BF80FACDA71FF077CAE23907 /* Pods-example.release.xcconfig */,
DE57DCC5FB89A01CC4F15D08 /* Pods-example.debug.xcconfig */,
38EC829E51DAE9347EEBB719 /* Pods-example.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -105,13 +105,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = (
19EDDD902D7748F2DDB86B4D /* [CP] Check Pods Manifest.lock */,
E64512420461EC9A112A889B /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
B56BFE2E5078044A567DF3A0 /* [CP] Embed Pods Frameworks */,
788C036C201D78CD57E01EDA /* [CP] Copy Pods Resources */,
ABAB5ABD407312F464F54478 /* [CP] Embed Pods Frameworks */,
73271984CC447AE7F2C70BD0 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -183,60 +183,60 @@
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";
};
19EDDD902D7748F2DDB86B4D /* [CP] Check Pods Manifest.lock */ = {
73271984CC447AE7F2C70BD0 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
788C036C201D78CD57E01EDA /* [CP] Copy Pods Resources */ = {
ABAB5ABD407312F464F54478 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist",
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
B56BFE2E5078044A567DF3A0 /* [CP] Embed Pods Frameworks */ = {
E64512420461EC9A112A889B /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
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 = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-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;
};
/* End PBXShellScriptBuildPhase section */
......@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C0D6DDD20DAAEA10C6999BD6 /* Pods-example.debug.xcconfig */;
baseConfigurationReference = DE57DCC5FB89A01CC4F15D08 /* Pods-example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......@@ -283,7 +283,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BF80FACDA71FF077CAE23907 /* Pods-example.release.xcconfig */;
baseConfigurationReference = 38EC829E51DAE9347EEBB719 /* Pods-example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
//QmsPlugin.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface QmsPluginUI : NSObject
+ (UIViewController *)makeViewController;
@end
NS_ASSUME_NONNULL_END
//
// QmsPluginFramework.h
// QmsPluginFramework
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
//! Project version number for QmsPluginFramework.
FOUNDATION_EXPORT double QmsPluginFrameworkVersionNumber;
//! Project version string for QmsPluginFramework.
FOUNDATION_EXPORT const unsigned char QmsPluginFrameworkVersionString[];
// Public headers
#import <QmsPluginFramework/QmsPlugin.h>
framework module QmsPluginFramework {
umbrella header "QmsPluginFramework.h"
export *
module * { export * }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Assets.car</key>
<data>
mDeq0E4lblXfFuIgkojjE5zY6hk=
</data>
<key>Headers/QmsPlugin.h</key>
<data>
46196FWEMwEuy8Dd/6jLyvdFEok=
</data>
<key>Headers/QmsPluginFramework.h</key>
<data>
jlHmWnGf08fskfPJb//4vaShDlo=
</data>
<key>Info.plist</key>
<data>
/JZsUWwwr7A+ZF0o001HQXRnzpI=
</data>
<key>Modules/module.modulemap</key>
<data>
BD9YnWBkeFmDcuOl4cAjALd/7yw=
</data>
</dict>
<key>files2</key>
<dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
0+eonZqFOeyimWUH94CuUhfXNEulhfi9YeuUE/wnX0s=
</data>
</dict>
<key>Headers/QmsPlugin.h</key>
<dict>
<key>hash2</key>
<data>
uuQ2Omt7lnrShVFriPpMiXZhx8kx6Km97GxC3m1ro+A=
</data>
</dict>
<key>Headers/QmsPluginFramework.h</key>
<dict>
<key>hash2</key>
<data>
/9Qe6QJ19ZC61+OfPl2+OZK1DI2Wa1lS++m7D41g3zI=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
e+P6RtiuicqWfZ3H2B43r960DPkFwUWqXKWU+w7OVD0=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
//QmsPlugin.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface QmsPluginUI : NSObject
+ (UIViewController *)makeViewController;
@end
NS_ASSUME_NONNULL_END
//
// QmsPluginFramework.h
// QmsPluginFramework
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
//! Project version number for QmsPluginFramework.
FOUNDATION_EXPORT double QmsPluginFrameworkVersionNumber;
//! Project version string for QmsPluginFramework.
FOUNDATION_EXPORT const unsigned char QmsPluginFrameworkVersionString[];
// Public headers
#import <QmsPluginFramework/QmsPlugin.h>
framework module QmsPluginFramework {
umbrella header "QmsPluginFramework.h"
export *
module * { export * }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Assets.car</key>
<data>
mDeq0E4lblXfFuIgkojjE5zY6hk=
</data>
<key>Headers/QmsPlugin.h</key>
<data>
46196FWEMwEuy8Dd/6jLyvdFEok=
</data>
<key>Headers/QmsPluginFramework.h</key>
<data>
jlHmWnGf08fskfPJb//4vaShDlo=
</data>
<key>Info.plist</key>
<data>
4pQwOIuYwsUH4Iiw1M402s3Ii8Y=
</data>
<key>Modules/module.modulemap</key>
<data>
BD9YnWBkeFmDcuOl4cAjALd/7yw=
</data>
</dict>
<key>files2</key>
<dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
0+eonZqFOeyimWUH94CuUhfXNEulhfi9YeuUE/wnX0s=
</data>
</dict>
<key>Headers/QmsPlugin.h</key>
<dict>
<key>hash2</key>
<data>
uuQ2Omt7lnrShVFriPpMiXZhx8kx6Km97GxC3m1ro+A=
</data>
</dict>
<key>Headers/QmsPluginFramework.h</key>
<dict>
<key>hash2</key>
<data>
/9Qe6QJ19ZC61+OfPl2+OZK1DI2Wa1lS++m7D41g3zI=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
e+P6RtiuicqWfZ3H2B43r960DPkFwUWqXKWU+w7OVD0=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
......@@ -6,6 +6,14 @@
+ (void)fetchAppointment:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)fetchGeneralInfo:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)submitAppointmentWithData:(NSDictionary *)appointmentData
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
+ (void)submitRepresentativeAppointmentWithData:(NSDictionary *)appointmentData
representativeData:(NSDictionary *)repData
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
+ (void)fetchCancelAppointment:(id)appointmentId
reason:(NSString *)reason
completion:(void (^)(NSDictionary *data, NSError *error))completion;
@end
......@@ -71,7 +71,7 @@
+ (void)fetchGeneralInfo:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/getUnitInfo?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/plan/getUnitInfo?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
......@@ -133,4 +133,235 @@
[task resume];
}
+ (void)submitAppointmentWithData:(NSDictionary *)appointmentData
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/makeAppointment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(@"🌍 SUBMIT (NO REP) URL: %@", urlString);
NSLog(@"📦 SUBMIT (NO REP) Body JSON: %@", appointmentData);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSDictionary *bodyDict = @{@"data": appointmentData};
NSError *jsonError;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:bodyDict options:0 error:&jsonError];
if (jsonError) {
NSLog(@"❌ JSON Serialization Error: %@", jsonError);
if (completion) completion(NO, nil, jsonError);
return;
}
request.HTTPBody = bodyData;
NSLog(@"🔄 SUBMIT (NO REP) Request started...");
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(), ^{
completion(NO, nil, error);
});
return;
}
NSError *parseError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (parseError) {
NSLog(@"⚠️ JSON Parse Error: %@", parseError);
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, nil, parseError);
});
return;
}
NSLog(@"✅ SUBMIT (NO REP) API Response:\n%@", json);
dispatch_async(dispatch_get_main_queue(), ^{
completion(YES, json, nil);
});
}];
[task resume];
}
+ (void)submitRepresentativeAppointmentWithData:(NSDictionary *)appointmentData
representativeData:(NSDictionary *)repData
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/makeAppointment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(@"🌍 SUBMIT (REP) URL: %@", urlString);
NSLog(@"📦 SUBMIT (REP) Appointment Data: %@", appointmentData);
NSLog(@"📎 SUBMIT (REP) Representative Data: %@", repData);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
forHTTPHeaderField:@"Content-Type"];
NSMutableData *body = [NSMutableData data];
// Add appointment fields
for (NSString *key in appointmentData) {
NSString *field = [self multipartFormField:key value:appointmentData[key] boundary:boundary];
[body appendData:[field dataUsingEncoding:NSUTF8StringEncoding]];
}
// Add representative fields
for (NSString *key in repData) {
id value = repData[key];
if ([value isKindOfClass:[NSDictionary class]] && value[@"filePath"]) {
NSLog(@"📎 Adding file for key '%@': %@", key, value[@"fileName"]);
[body appendData:[self multipartFormFile:value[@"filePath"]
fieldName:key
fileName:value[@"fileName"]
mimeType:value[@"mimeType"]
boundary:boundary]];
} else {
NSString *field = [self multipartFormField:key value:value boundary:boundary];
[body appendData:[field dataUsingEncoding:NSUTF8StringEncoding]];
}
}
// Close multipart
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = body;
NSLog(@"🔄 SUBMIT (REP) Request started with multipart body (%lu bytes)", (unsigned long)body.length);
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(), ^{
completion(NO, nil, error);
});
return;
}
NSError *parseError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (parseError) {
NSLog(@"⚠️ JSON Parse Error: %@", parseError);
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, nil, parseError);
});
return;
}
NSLog(@"✅ SUBMIT (REP) API Response:\n%@", json);
dispatch_async(dispatch_get_main_queue(), ^{
completion(YES, json, nil);
});
}];
[task resume];
}
+ (void)fetchCancelAppointment:(id)appointmentId
reason:(NSString *)reason
completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/appointment/cancelAppointment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
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": @{
@"project_id": @"8",
@"appointment_id": appointmentId,
@"reason": reason
}
};
// ✅ 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(@"✅ Void Issues API Response:\n%@", jsonResponse);
}
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(jsonResponse, jsonParseError);
});
}];
[task resume];
}
#pragma mark - Helper Methods
+ (NSString *)multipartFormField:(NSString *)name value:(NSString *)value boundary:(NSString *)boundary {
return [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"data[%@]\"\r\n\r\n%@\r\n", boundary, name, value];
}
+ (NSData *)multipartFormFile:(NSString *)filePath fieldName:(NSString *)fieldName fileName:(NSString *)fileName mimeType:(NSString *)mimeType boundary:(NSString *)boundary {
NSMutableData *data = [NSMutableData data];
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"data[%@]\"; filename=\"%@\"\r\n", fieldName, fileName] dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimeType] dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:fileData];
[data appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
return data;
}
@end
......@@ -2,11 +2,16 @@
#import <UIKit/UIKit.h>
@interface AppointmentConfirmationViewController : UIViewController
@interface AppointmentConfirmationViewController : UIViewController
@property (nonatomic, strong) NSString *slotId;
@property (nonatomic, strong) NSString *startDate;
@property (nonatomic, strong) NSString *startTime;
@property (nonatomic, strong) NSString *projectId;
@property (nonatomic, strong) NSString *projectName;
@property (nonatomic, strong) NSString *termsAndCondition;
@property (nonatomic, strong) NSString *appointmentType;
@property (nonatomic, strong) NSArray *representativeList;
@end
// AppointmentConfirmationViewController.mm
#import "AppointmentConfirmationViewController.h"
#import <WebKit/WebKit.h>
#import <UIKit/UIKit.h>
#import "AppointmentAPIClient.h"
@interface AppointmentConfirmationViewController ()
@interface AppointmentConfirmationViewController () <WKNavigationDelegate>
// Top Bar
@property (nonatomic, strong) UIView *navBar;
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *closeButton;
@property (nonatomic, strong) UIButton *backButton;
// Scroll
@property (nonatomic, strong) UIScrollView *scrollView;
......@@ -16,16 +17,41 @@
// Summary Section
@property (nonatomic, strong) UILabel *unitLabel;
@property (nonatomic, strong) UILabel *unitLabelName;
@property (nonatomic, strong) UIView *stepBanner;
@property (nonatomic, strong) UIView *summaryCard;
@property (nonatomic, strong) UILabel *appointmentTitle;
@property (nonatomic, strong) UILabel *appointmentTypeLabel;
@property (nonatomic, strong) UILabel *dateLabel;
@property (nonatomic, strong) UILabel *timeLabel;
@property (nonatomic, strong) NSLayoutConstraint *htmlHeightConstraint;
// Representative Section
@property (nonatomic, strong) UIView *representativeSection;
@property (nonatomic, strong) UILabel *repTitleLabel;
@property (nonatomic, strong) UIButton *repToggleButton;
@property (nonatomic, strong) UIView *repOverlayContainer;
@property (nonatomic, strong) UIStackView *repInputsContainer;
@property (nonatomic, assign) BOOL blnTickRepresentative;
@property (nonatomic, strong) UIImageView *icPhotoView;
@property (nonatomic, strong) UIImageView *letterPhotoView;
// Optional input fields
@property (nonatomic, strong) UITextField *repNameField;
@property (nonatomic, strong) UITextField *repICField;
@property (nonatomic, strong) UITextField *repEmailField;
@property (nonatomic, strong) UITextField *repContactField;
@property (nonatomic, strong) UILabel *repNameTitle;
@property (nonatomic, strong) UILabel *repICTitle;
@property (nonatomic, strong) UILabel *repEmailTitle;
@property (nonatomic, strong) UILabel *repContactTitle;
@property (nonatomic, strong) NSString *icPhoto;
@property (nonatomic, strong) NSString *letterPhoto;
// Overlay for full-screen selection
@property (nonatomic, strong) UIView *repOverlayView;
@property (nonatomic, strong) NSLayoutConstraint *repInputsHeightConstraint;
// T&C
@property (nonatomic, strong) UILabel *tcTitleLabel;
......@@ -39,190 +65,354 @@
// Modal Placeholder
@property (nonatomic, strong) UIView *dimOverlay;
@end
@implementation AppointmentConfirmationViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self setupNavBar];
[self setupScrollView];
[self setupContentUI];
[self setupBottomBar];
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
NSLog(@"📦 Data received by appointmentConfirmation:"
"\n slotId: %@"
"\n startDate: %@"
"\n startTime: %@"
"\n projectId: %@"
"\n projectName: %@"
"\n appointmentType: %@"
"\n reprensetativeList: %@"
"\n terms & condition: %@",
self.slotId,
self.startDate,
self.startTime,
self.projectId,
self.projectName,
self.appointmentType,
self.representativeList,
self.termsAndCondition);
self.repInputsContainer.hidden = YES;
self.icPhotoView = [[UIImageView alloc] init];
self.icPhotoView.contentMode = UIViewContentModeScaleAspectFill;
self.icPhotoView.clipsToBounds = YES;
self.icPhotoView.layer.cornerRadius = 8;
self.icPhotoView.hidden = YES; // hidden until a rep is selected
self.letterPhotoView = [[UIImageView alloc] init];
self.letterPhotoView.contentMode = UIViewContentModeScaleAspectFill;
self.letterPhotoView.clipsToBounds = YES;
self.letterPhotoView.layer.cornerRadius = 8;
self.letterPhotoView.hidden = YES;
[self setupHeader];
[self setupBottomBar];
[self setupScrollView];
[self setupContentUI];
}
#pragma mark - Navigation Bar
- (void)setupNavBar {
self.navBar = [[UIView alloc] init];
self.navBar.backgroundColor = [UIColor colorWithRed:0 green:0.45 blue:0.55 alpha:1];
self.navBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.navBar];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Make Appointment";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:18];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.navBar addSubview:self.titleLabel];
// Close Button
self.closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.closeButton setImage:[UIImage imageNamed:@"icon_white_cancel"] forState:UIControlStateNormal];
self.closeButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.navBar addSubview:self.closeButton];
// Constraints
[NSLayoutConstraint activateConstraints:@[
[self.navBar.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.navBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.navBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.navBar.heightAnchor constraintEqualToConstant:48],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.navBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.navBar.centerYAnchor],
[self.closeButton.leadingAnchor constraintEqualToAnchor:self.navBar.leadingAnchor constant:12],
[self.closeButton.centerYAnchor constraintEqualToAnchor:self.navBar.centerYAnchor],
[self.closeButton.widthAnchor constraintEqualToConstant:30],
[self.closeButton.heightAnchor constraintEqualToConstant:30],
]];
- (void)setupHeader {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
//Header
self.headerBar = [[UIView alloc] init];
self.headerBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // #006D8D
[self.view addSubview:self.headerBar];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[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],
]];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Make Appointment";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
// Back button
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:[UIImage systemImageNamed:@"xmark"] forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
[self.backButton addTarget:self
action:@selector(cancelPressed:)
forControlEvents:UIControlEventTouchUpInside];
for (UIView *sub in @[self.titleLabel, self.backButton]) {
sub.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:sub];
}
[NSLayoutConstraint activateConstraints:@[
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
]];
}
#pragma mark - ScrollView
- (void)setupScrollView {
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
self.contentView = [[UIView alloc] init];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:self.contentView];
[NSLayoutConstraint activateConstraints:@[
[self.scrollView.topAnchor constraintEqualToAnchor:self.navBar.bottomAnchor],
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.contentView.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor],
[self.contentView.leadingAnchor constraintEqualToAnchor:self.scrollView.leadingAnchor],
[self.contentView.trailingAnchor constraintEqualToAnchor:self.scrollView.trailingAnchor],
[self.contentView.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor],
[self.contentView.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor],
]];
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
self.contentView = [[UIView alloc] init];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:self.contentView];
[NSLayoutConstraint activateConstraints:@[
// scrollView sits between header and bottomBar
[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.bottomBar.topAnchor], // anchor to bottomBar
// contentView fills scrollView
[self.contentView.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor],
[self.contentView.leadingAnchor constraintEqualToAnchor:self.scrollView.leadingAnchor],
[self.contentView.trailingAnchor constraintEqualToAnchor:self.scrollView.trailingAnchor],
[self.contentView.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor],
[self.contentView.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor],
]];
}
#pragma mark - Main UI
- (void)setupContentUI {
// Unit Label
// --- Unit Labels ---
self.unitLabel = [[UILabel alloc] init];
self.unitLabel.text = @"ProjectName, Unit 12-34";
self.unitLabel.font = [UIFont boldSystemFontOfSize:20];
self.unitLabel.text = @"For Unit No.:";
self.unitLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightThin];
self.unitLabel.textColor = [UIColor grayColor];
self.unitLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.unitLabel];
// Step Banner
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.contentView addSubview:self.unitLabelName];
// --- Step Banner ---
self.stepBanner = [[UIView alloc] init];
self.stepBanner.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1];
self.stepBanner.layer.cornerRadius = 12;
self.stepBanner.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.stepBanner];
UILabel *stepLabel = [[UILabel alloc] init];
stepLabel.text = @"Step 3: Appointment Summary";
stepLabel.text = @"Step 3/3: Confirm your Appointment";
stepLabel.font = [UIFont systemFontOfSize:15];
stepLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.stepBanner addSubview:stepLabel];
// Summary Card
// --- Summary Card ---
self.summaryCard = [[UIView alloc] init];
self.summaryCard.backgroundColor = [UIColor colorWithRed:0 green:0.23 blue:0.30 alpha:1];
self.summaryCard.layer.cornerRadius = 12;
self.summaryCard.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.summaryCard];
self.appointmentTitle = [[UILabel alloc] init];
self.appointmentTitle.text = @"Your Appointment Summary";
self.appointmentTitle.textColor = UIColor.whiteColor;
self.appointmentTitle.font = [UIFont boldSystemFontOfSize:20];
self.appointmentTitle.translatesAutoresizingMaskIntoConstraints = NO;
[self.summaryCard addSubview:self.appointmentTitle];
self.appointmentTypeLabel = [[UILabel alloc] init];
self.appointmentTypeLabel.text = @"Inspection";
self.appointmentTypeLabel.text = [self formattedAppointmentType:self.appointmentType];
self.appointmentTypeLabel.textColor = UIColor.whiteColor;
self.appointmentTypeLabel.font = [UIFont boldSystemFontOfSize:22];
self.appointmentTypeLabel.font = [UIFont boldSystemFontOfSize:28];
self.appointmentTypeLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.summaryCard addSubview:self.appointmentTypeLabel];
self.dateLabel = [[UILabel alloc] init];
self.dateLabel.text = @"20 Jan 2025";
self.dateLabel.text = self.startDate;
self.dateLabel.textColor = UIColor.whiteColor;
self.dateLabel.font = [UIFont systemFontOfSize:18];
self.dateLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.summaryCard addSubview:self.dateLabel];
self.timeLabel = [[UILabel alloc] init];
self.timeLabel.text = @"09:00 AM";
self.timeLabel.text = self.startTime;
self.timeLabel.textColor = UIColor.whiteColor;
self.timeLabel.font = [UIFont systemFontOfSize:18];
self.timeLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.summaryCard addSubview:self.timeLabel];
// Representative Section
self.representativeSection = [[UIView alloc] init];
self.representativeSection.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.representativeSection];
self.repTitleLabel = [[UILabel alloc] init];
self.repTitleLabel.text = @"Representative Attending?";
self.repTitleLabel.font = [UIFont boldSystemFontOfSize:17];
self.repTitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.repToggleButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.repToggleButton.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
self.repToggleButton.layer.cornerRadius = 14;
self.repToggleButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.representativeSection addSubview:self.repTitleLabel];
[self.representativeSection addSubview:self.repToggleButton];
// Terms & Conditions
UIStackView *dateTimeStack = [[UIStackView alloc] initWithArrangedSubviews:@[self.dateLabel, self.timeLabel]];
dateTimeStack.axis = UILayoutConstraintAxisHorizontal;
dateTimeStack.spacing = 20;
dateTimeStack.alignment = UIStackViewAlignmentLeading;
dateTimeStack.translatesAutoresizingMaskIntoConstraints = NO;
[self.summaryCard addSubview:dateTimeStack];
// --- Representative Section ---
[self setupRepresentativeSection]; // Uses stackview-based refactor from previous step
// --- Terms & Conditions ---
self.tcTitleLabel = [[UILabel alloc] init];
self.tcTitleLabel.text = @"Terms & Conditions";
self.tcTitleLabel.font = [UIFont boldSystemFontOfSize:16];
self.tcTitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.tcTitleLabel];
self.htmlView = [[WKWebView alloc] init];
self.htmlView.navigationDelegate = self;
self.htmlView.scrollView.scrollEnabled = NO;
self.htmlView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.htmlView];
// Layout
self.htmlHeightConstraint = [self.htmlView.heightAnchor constraintEqualToConstant:50];
self.htmlHeightConstraint.active = YES;
[self.htmlView loadHTMLString:self.termsAndCondition baseURL:nil];
// --- Layout Constraints ---
[NSLayoutConstraint activateConstraints:@[
[self.unitLabel.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:20],
[self.unitLabel.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:20],
[self.stepBanner.topAnchor constraintEqualToAnchor:self.unitLabel.bottomAnchor constant:20],
// Unit Labels
[self.unitLabel.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:16],
[self.unitLabel.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.unitLabel.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[self.unitLabelName.topAnchor constraintEqualToAnchor:self.unitLabel.bottomAnchor constant:8],
[self.unitLabelName.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.unitLabelName.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
// Step Banner
[self.stepBanner.topAnchor constraintEqualToAnchor:self.unitLabelName.bottomAnchor constant:20],
[self.stepBanner.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.stepBanner.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[self.stepBanner.heightAnchor constraintEqualToConstant:50],
[stepLabel.centerYAnchor constraintEqualToAnchor:self.stepBanner.centerYAnchor],
[stepLabel.leadingAnchor constraintEqualToAnchor:self.stepBanner.leadingAnchor constant:12],
// Summary Card
[self.summaryCard.topAnchor constraintEqualToAnchor:self.stepBanner.bottomAnchor constant:20],
[self.summaryCard.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.summaryCard.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.appointmentTypeLabel.topAnchor constraintEqualToAnchor:self.summaryCard.topAnchor constant:20],
[self.summaryCard.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:0],
[self.summaryCard.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:0],
[self.appointmentTitle.topAnchor constraintEqualToAnchor:self.summaryCard.topAnchor constant:20],
[self.appointmentTitle.leadingAnchor constraintEqualToAnchor:self.summaryCard.leadingAnchor constant:20],
[self.appointmentTypeLabel.topAnchor constraintEqualToAnchor:self.appointmentTitle.bottomAnchor constant:8],
[self.appointmentTypeLabel.leadingAnchor constraintEqualToAnchor:self.summaryCard.leadingAnchor constant:20],
[dateTimeStack.topAnchor constraintEqualToAnchor:self.appointmentTypeLabel.bottomAnchor constant:16],
[dateTimeStack.leadingAnchor constraintEqualToAnchor:self.summaryCard.leadingAnchor constant:20],
[dateTimeStack.bottomAnchor constraintEqualToAnchor:self.summaryCard.bottomAnchor constant:-20],
// Terms & Conditions below representative stack
[self.tcTitleLabel.topAnchor constraintEqualToAnchor:self.repInputsContainer.bottomAnchor constant:30],
[self.tcTitleLabel.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.tcTitleLabel.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[self.htmlView.topAnchor constraintEqualToAnchor:self.tcTitleLabel.bottomAnchor constant:12],
[self.htmlView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:20],
[self.htmlView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-20],
[self.htmlView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-40],
]];
}
[self.dateLabel.topAnchor constraintEqualToAnchor:self.appointmentTypeLabel.bottomAnchor constant:16],
[self.dateLabel.leadingAnchor constraintEqualToAnchor:self.summaryCard.leadingAnchor constant:20],
[self.timeLabel.topAnchor constraintEqualToAnchor:self.dateLabel.bottomAnchor constant:8],
[self.timeLabel.leadingAnchor constraintEqualToAnchor:self.summaryCard.leadingAnchor constant:20],
[self.timeLabel.bottomAnchor constraintEqualToAnchor:self.summaryCard.bottomAnchor constant:-20],
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[webView evaluateJavaScript:@"document.body.scrollHeight;" completionHandler:^(id result, NSError *error) {
if (!error && result) {
CGFloat contentHeight = [result floatValue];
// Update the dynamic constraint
self.htmlHeightConstraint.constant = contentHeight;
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
}];
}
}];
}
// Representative
#pragma mark - representative section
- (void)setupRepresentativeSection {
// --- Representative Section Container ---
self.representativeSection = [[UIView alloc] init];
self.representativeSection.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.representativeSection];
self.repTitleLabel = [[UILabel alloc] init];
self.repTitleLabel.text = @"Representative Attending?";
self.repTitleLabel.font = [UIFont boldSystemFontOfSize:17];
self.repTitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.repToggleButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.repToggleButton.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
self.repToggleButton.layer.cornerRadius = 14;
self.repToggleButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.repToggleButton addTarget:self
action:@selector(toggleRepresentative:)
forControlEvents:UIControlEventTouchUpInside];
[self.representativeSection addSubview:self.repTitleLabel];
[self.representativeSection addSubview:self.repToggleButton];
// --- Representative Inputs Stack ---
self.repNameField = [[UITextField alloc] init];
self.repNameField.backgroundColor = [UIColor lightGrayColor];
self.repICField = [[UITextField alloc] init];
self.repICField.backgroundColor = [UIColor lightGrayColor];
self.repEmailField = [[UITextField alloc] init];
self.repEmailField.backgroundColor = [UIColor lightGrayColor];
self.repContactField = [[UITextField alloc] init];
self.repContactField.backgroundColor = [UIColor lightGrayColor];
UILabel *repNameTitle = [[UILabel alloc] init];
repNameTitle.text = @"Name"; repNameTitle.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
UILabel *repICTitle = [[UILabel alloc] init];
repICTitle.text = @"NRIC / Passport"; repICTitle.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
UILabel *repEmailTitle = [[UILabel alloc] init];
repEmailTitle.text = @"Email"; repEmailTitle.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
UILabel *repContactTitle = [[UILabel alloc] init];
repContactTitle.text = @"Contact No."; repContactTitle.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
// Image row
self.icPhotoView = [[UIImageView alloc] init];
self.letterPhotoView = [[UIImageView alloc] init];
self.icPhotoView.contentMode = UIViewContentModeScaleAspectFill; self.icPhotoView.clipsToBounds = YES; self.icPhotoView.layer.cornerRadius = 8;
self.letterPhotoView.contentMode = UIViewContentModeScaleAspectFill; self.letterPhotoView.clipsToBounds = YES; self.letterPhotoView.layer.cornerRadius = 8;
UIStackView *imageRow = [[UIStackView alloc] initWithArrangedSubviews:@[self.icPhotoView, self.letterPhotoView]];
imageRow.axis = UILayoutConstraintAxisHorizontal;
imageRow.distribution = UIStackViewDistributionFillEqually;
imageRow.spacing = 12;
imageRow.translatesAutoresizingMaskIntoConstraints = NO;
self.repInputsContainer = [[UIStackView alloc] initWithArrangedSubviews:@[
imageRow,
repNameTitle, self.repNameField,
repICTitle, self.repICField,
repEmailTitle, self.repEmailField,
repContactTitle, self.repContactField
]];
self.repInputsContainer.axis = UILayoutConstraintAxisVertical;
self.repInputsContainer.spacing = 8;
self.repInputsContainer.translatesAutoresizingMaskIntoConstraints = NO;
self.repInputsContainer.hidden = YES; // Initially hidden
[self.contentView addSubview:self.repInputsContainer];
// --- Constraints ---
[NSLayoutConstraint activateConstraints:@[
// Representative Section
[self.representativeSection.topAnchor constraintEqualToAnchor:self.summaryCard.bottomAnchor constant:30],
[self.representativeSection.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:20],
[self.representativeSection.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-20],
......@@ -236,60 +426,243 @@
[self.repToggleButton.widthAnchor constraintEqualToConstant:28],
[self.repToggleButton.heightAnchor constraintEqualToConstant:28],
// Terms & Conditions
[self.tcTitleLabel.topAnchor constraintEqualToAnchor:self.representativeSection.bottomAnchor constant:30],
[self.tcTitleLabel.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:20],
[self.htmlView.topAnchor constraintEqualToAnchor:self.tcTitleLabel.bottomAnchor constant:12],
[self.htmlView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:20],
[self.htmlView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-20],
[self.htmlView.heightAnchor constraintEqualToConstant:200],
[self.htmlView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-40],
// Rep inputs stack
[self.repInputsContainer.topAnchor constraintEqualToAnchor:self.representativeSection.bottomAnchor constant:8],
[self.repInputsContainer.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.repInputsContainer.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
]];
// Image row height
[self.icPhotoView.heightAnchor constraintEqualToConstant:120].active = YES;
[self.letterPhotoView.heightAnchor constraintEqualToConstant:120].active = YES;
}
#pragma mark - Bottom Bar
- (void)setupBottomBar {
self.bottomBar = [[UIView alloc] init];
self.bottomBar.backgroundColor = UIColor.whiteColor;
self.bottomBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.bottomBar];
self.editButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.editButton setTitle:@"Edit" forState:UIControlStateNormal];
self.editButton.layer.borderColor = [UIColor colorWithRed:0 green:0.45 blue:0.55 alpha:1].CGColor;
self.editButton.layer.borderWidth = 1;
self.editButton.layer.cornerRadius = 20;
self.editButton.translatesAutoresizingMaskIntoConstraints = NO;
self.confirmButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.confirmButton setTitle:@"Confirm" forState:UIControlStateNormal];
self.confirmButton.backgroundColor = [UIColor colorWithRed:0 green:0.45 blue:0.55 alpha:1];
[self.confirmButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
self.confirmButton.layer.cornerRadius = 20;
self.confirmButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.bottomBar addSubview:self.editButton];
[self.bottomBar addSubview:self.confirmButton];
self.bottomBar = [[UIView alloc] init];
self.bottomBar.backgroundColor = UIColor.whiteColor;
self.bottomBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.bottomBar];
self.editButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.editButton setTitle:@"Edit" forState:UIControlStateNormal];
self.editButton.layer.borderColor = [UIColor colorWithRed:0 green:0.45 blue:0.55 alpha:1].CGColor;
self.editButton.layer.borderWidth = 1;
self.editButton.layer.cornerRadius = 20;
self.editButton.translatesAutoresizingMaskIntoConstraints = NO;
self.confirmButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.confirmButton setTitle:@"Confirm" forState:UIControlStateNormal];
self.confirmButton.backgroundColor = [UIColor colorWithRed:0 green:0.45 blue:0.55 alpha:1];
[self.confirmButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
self.confirmButton.layer.cornerRadius = 20;
self.confirmButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.confirmButton addTarget:self
action:@selector(confirmPressed)
forControlEvents:UIControlEventTouchUpInside];
[self.bottomBar addSubview:self.editButton];
[self.bottomBar addSubview:self.confirmButton];
[NSLayoutConstraint activateConstraints:@[
[self.bottomBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.bottomBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.bottomBar.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.bottomBar.heightAnchor constraintEqualToConstant:70],
[self.editButton.leadingAnchor constraintEqualToAnchor:self.bottomBar.leadingAnchor constant:20],
[self.editButton.centerYAnchor constraintEqualToAnchor:self.bottomBar.centerYAnchor],
[self.editButton.widthAnchor constraintEqualToAnchor:self.bottomBar.widthAnchor multiplier:0.45],
[self.editButton.heightAnchor constraintEqualToConstant:48],
[self.confirmButton.trailingAnchor constraintEqualToAnchor:self.bottomBar.trailingAnchor constant:-20],
[self.confirmButton.centerYAnchor constraintEqualToAnchor:self.bottomBar.centerYAnchor],
[self.confirmButton.widthAnchor constraintEqualToAnchor:self.bottomBar.widthAnchor multiplier:0.45],
[self.confirmButton.heightAnchor constraintEqualToConstant:48],
]];
}
[NSLayoutConstraint activateConstraints:@[
[self.bottomBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.bottomBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.bottomBar.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.bottomBar.heightAnchor constraintEqualToConstant:70],
[self.editButton.leadingAnchor constraintEqualToAnchor:self.bottomBar.leadingAnchor constant:20],
[self.editButton.centerYAnchor constraintEqualToAnchor:self.bottomBar.centerYAnchor],
[self.editButton.widthAnchor constraintEqualToAnchor:self.bottomBar.widthAnchor multiplier:0.45],
[self.editButton.heightAnchor constraintEqualToConstant:48],
[self.confirmButton.trailingAnchor constraintEqualToAnchor:self.bottomBar.trailingAnchor constant:-20],
[self.confirmButton.centerYAnchor constraintEqualToAnchor:self.bottomBar.centerYAnchor],
[self.confirmButton.widthAnchor constraintEqualToAnchor:self.bottomBar.widthAnchor multiplier:0.45],
[self.confirmButton.heightAnchor constraintEqualToConstant:48],
]];
#pragma mark - representative
- (void)toggleRepresentative:(UIButton *)sender {
if (self.repInputsContainer.isHidden) {
// First tap → show picker
[self showRepresentativePicker];
} else {
// Already showing → hide
[self hideRepresentativeInputs];
}
}
@end
- (void)showRepresentativeInputs {
self.repInputsContainer.hidden = NO;
self.icPhotoView.hidden = NO;
self.letterPhotoView.hidden = NO;
[UIView animateWithDuration:0.3 animations:^{
[self.contentView layoutIfNeeded];
}];
}
- (void)hideRepresentativeInputs {
[UIView animateWithDuration:0.3 animations:^{
self.repInputsContainer.hidden = YES;
self.icPhotoView.hidden = YES;
self.letterPhotoView.hidden = YES;
[self.contentView layoutIfNeeded];
}];
// Optional: clear fields
self.repNameField.text = @"";
self.repICField.text = @"";
self.repEmailField.text = @"";
self.repContactField.text = @"";
}
#pragma mark - helpers
- (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
if (self.navigationController) {
[self.navigationController popToRootViewControllerAnimated:YES];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}]];
[self presentViewController:a animated:YES completion:nil];
}
- (NSString *)formattedAppointmentType:(NSString *)type {
if (![type isKindOfClass:[NSString class]] || type.length == 0) {
return @"";
}
// 1. Replace underscores with spaces
NSString *spaced = [type stringByReplacingOccurrencesOfString:@"_" withString:@" "];
// 2. Capitalize each word
return [spaced capitalizedString];
}
- (void)showRepresentativePicker {
if (!self.representativeList || self.representativeList.count == 0) {
NSLog(@"⚠️ No representatives to display");
return;
}
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Select Representative"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
for (NSDictionary *rep in self.representativeList) {
NSString *repName = rep[@"name"] ?: @"Unnamed";
UIAlertAction *action = [UIAlertAction actionWithTitle:repName
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"✅ Selected representative: %@", repName);
// Fill the form fields
self.repNameField.text = rep[@"name"] ?: @"";
self.repICField.text = rep[@"ic_passport"] ?: @"";
self.repEmailField.text = rep[@"email"] ?: @"";
self.repContactField.text = rep[@"phone"] ?: @"";
self.icPhoto = rep[@"ic_photo"];
self.letterPhoto = rep[@"letter"];
[self loadImage:rep[@"ic_photo"] intoImageView:self.icPhotoView];
[self loadImage:rep[@"letter"] intoImageView:self.letterPhotoView];
// Optionally show inputs if they are hidden
[self showRepresentativeInputs];
}];
[alert addAction:action];
}
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)selectRepresentativeOther {
NSLog(@"✅ Selected 'Other' representative");
self.repNameField.text = @"";
self.repICField.text = @"";
self.repEmailField.text = @"";
self.repContactField.text = @"";
// Hide overlay
[UIView animateWithDuration:0.25 animations:^{
self.repOverlayContainer.alpha = 0;
} completion:^(BOOL finished) {
[self.repOverlayContainer.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}];
// Make sure toggle is ticked
self.blnTickRepresentative = YES;
// Show the representative input fields
[self showRepresentativeInputs];
}
- (void)loadImage:(NSString *)urlString intoImageView:(UIImageView *)imageView {
if (!urlString) return;
NSURL *url = [NSURL URLWithString:urlString];
if (!url) return;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data && !error) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
});
}
}
}];
[task resume];
}
-(void)confirmPressed {
NSDictionary *appointmentData = @{
@"project_id": @"8",
@"slot_id": self.slotId,
@"appointment_type": self.appointmentType,
@"user_id": @"2170",
@"appointment_attendee_type": @"representative",
@"drawing_plan_id": @"135",
};
if (self.blnTickRepresentative) {
NSDictionary *repData = @{
@"representative_name": self.repNameField.text,
@"representative_ic_passport": self.repICField.text,
@"representative_email": self.repEmailField.text,
@"representative_contact": self.repContactField.text,
// files example
@"ic_photo": self.icPhoto,
@"representative_letter": self.letterPhoto,
};
[AppointmentAPIClient submitRepresentativeAppointmentWithData:appointmentData representativeData:repData completion:^(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error) {
// handle response
}];
} else {
[AppointmentAPIClient submitAppointmentWithData:appointmentData completion:^(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error) {
// handle response
}];
}
}
@end
......@@ -2,6 +2,7 @@
#import "AppointmentDetailsViewController.h"
#import <WebKit/WebKit.h>
#import "AppointmentAPIClient.h"
@interface AppointmentDetailsViewController ()
......@@ -9,6 +10,7 @@
@property (nonatomic, strong) UIView *navBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UIView *unitInfoHeader;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView;
......@@ -20,6 +22,7 @@
@property (nonatomic, strong) UIView *summaryCard;
@property (nonatomic, strong) UIView *repCard;
@property (nonatomic, strong) UIView *buttonSection;
@property (nonatomic, strong) UIView *tabsBar;
@property (nonatomic, strong) UIView *loadingOverlay;
......@@ -35,13 +38,13 @@
[self setupNavBar];
[self setupScrollView];
[self setupStatusBanner];
[self setupUnitInfoHeader];
[self setupSummaryCard];
[self setupRepresentativeCard];
[self setupButtonSection];
[self setupLoadingOverlay];
// load appointment details
[self loadAppointmentDetails];
[self applyAppointmentDataToUI];
}
#pragma mark - Navigation Bar
......@@ -81,6 +84,67 @@
]];
}
#pragma mark - Unit Info Header
- (void)setupUnitInfoHeader {
UIView *header = [[UIView alloc] init];
header.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:header];
UILabel *unitText = [[UILabel alloc] init];
unitText.text = @"For Unit No.:";
unitText.textColor = UIColor.darkGrayColor;
unitText.font = [UIFont systemFontOfSize:14];
unitText.translatesAutoresizingMaskIntoConstraints = NO;
[header addSubview:unitText];
UILabel *unitNumber = [[UILabel alloc] init];
unitNumber.text = self.appointmentData[@"unit_no"];
unitNumber.font = [UIFont boldSystemFontOfSize:22];
unitNumber.translatesAutoresizingMaskIntoConstraints = NO;
[header addSubview:unitNumber];
UILabel *unitType = [[UILabel alloc] init];
unitType.text = self.appointmentData[@"unit_type"];
unitType.font = [UIFont systemFontOfSize:14];
unitType.textColor = UIColor.grayColor;
unitType.translatesAutoresizingMaskIntoConstraints = NO;
[header addSubview:unitType];
UIImageView *floorPlan = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"placeholder_floorplan"]];
floorPlan.layer.cornerRadius = 8;
floorPlan.layer.borderColor = [UIColor colorWithWhite:0.85 alpha:1].CGColor;
floorPlan.layer.borderWidth = 1;
floorPlan.clipsToBounds = YES;
floorPlan.translatesAutoresizingMaskIntoConstraints = NO;
[header addSubview:floorPlan];
[NSLayoutConstraint activateConstraints:@[
[header.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:20],
[header.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[header.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[unitText.topAnchor constraintEqualToAnchor:header.topAnchor],
[unitText.leadingAnchor constraintEqualToAnchor:header.leadingAnchor],
[unitNumber.topAnchor constraintEqualToAnchor:unitText.bottomAnchor constant:4],
[unitNumber.leadingAnchor constraintEqualToAnchor:header.leadingAnchor],
[unitType.topAnchor constraintEqualToAnchor:unitNumber.bottomAnchor constant:2],
[unitType.leadingAnchor constraintEqualToAnchor:header.leadingAnchor],
[floorPlan.centerYAnchor constraintEqualToAnchor:header.centerYAnchor],
[floorPlan.trailingAnchor constraintEqualToAnchor:header.trailingAnchor],
[floorPlan.widthAnchor constraintEqualToConstant:120],
[floorPlan.heightAnchor constraintEqualToConstant:120],
[header.bottomAnchor constraintEqualToAnchor:unitType.bottomAnchor constant:20]
]];
self.unitInfoHeader = header;
}
#pragma mark - ScrollView
- (void)setupScrollView {
......@@ -167,9 +231,80 @@
[title.leadingAnchor constraintEqualToAnchor:self.summaryCard.leadingAnchor constant:16],
]];
// Add additional appointment fields later in applyAppointmentDataToUI
UILabel *(^makeField)(NSString*, NSString*) = ^UILabel*(NSString *label, NSString *value) {
UILabel *field = [[UILabel alloc] init];
field.numberOfLines = 0;
field.font = [UIFont systemFontOfSize:15];
field.text = [NSString stringWithFormat:@"%@ %@", label, value];
field.translatesAutoresizingMaskIntoConstraints = NO;
[self.summaryCard addSubview:field];
return field;
};
UILabel *typeField = makeField(@"Type :", self.appointmentData[@"type"]);
UILabel *dateField = makeField(@"Date :", self.appointmentData[@"date"]);
UILabel *slotField = makeField(@"Time Slot :", self.appointmentData[@"time"]);
UILabel *locField = makeField(@"Location :", self.appointmentData[@"location"]);
UIButton *calendarBtn = [UIButton buttonWithType:UIButtonTypeCustom];
calendarBtn.layer.cornerRadius = 12;
calendarBtn.layer.borderWidth = 1;
calendarBtn.layer.borderColor = [UIColor colorWithWhite:0.8 alpha:1].CGColor;
calendarBtn.titleLabel.font = [UIFont systemFontOfSize:15];
[calendarBtn setImage:[UIImage systemImageNamed:@"calendar"] forState:UIControlStateNormal];
[calendarBtn setTitle:@" Add To Calendar" forState:UIControlStateNormal];
[calendarBtn setTitleColor:UIColor.blackColor forState:UIControlStateNormal];
calendarBtn.translatesAutoresizingMaskIntoConstraints = NO;
[self.summaryCard addSubview:calendarBtn];
}
#pragma mark - Summary Tabs
- (void)setupTabs {
UIView *tabs = [[UIView alloc] init];
tabs.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:tabs];
UIButton *summaryBtn = [UIButton buttonWithType:UIButtonTypeCustom];
summaryBtn.backgroundColor = [UIColor colorWithRed:0 green:0.45 blue:0.55 alpha:1];
[summaryBtn setTitle:@"Summary" forState:UIControlStateNormal];
summaryBtn.titleLabel.font = [UIFont boldSystemFontOfSize:15];
summaryBtn.layer.cornerRadius = 20;
summaryBtn.translatesAutoresizingMaskIntoConstraints = NO;
UIButton *ownerBtn = [UIButton buttonWithType:UIButtonTypeCustom];
ownerBtn.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
ownerBtn.layer.cornerRadius = 20;
[ownerBtn setTitle:@"Owner Info" forState:UIControlStateNormal];
ownerBtn.titleLabel.font = [UIFont systemFontOfSize:15];
ownerBtn.translatesAutoresizingMaskIntoConstraints = NO;
[tabs addSubview:summaryBtn];
[tabs addSubview:ownerBtn];
[NSLayoutConstraint activateConstraints:@[
[tabs.topAnchor constraintEqualToAnchor:self.unitInfoHeader.bottomAnchor constant:20],
[tabs.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[tabs.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[summaryBtn.topAnchor constraintEqualToAnchor:tabs.topAnchor],
[summaryBtn.leadingAnchor constraintEqualToAnchor:tabs.leadingAnchor],
[summaryBtn.widthAnchor constraintEqualToAnchor:tabs.widthAnchor multiplier:0.5],
[summaryBtn.heightAnchor constraintEqualToConstant:40],
[ownerBtn.topAnchor constraintEqualToAnchor:tabs.topAnchor],
[ownerBtn.trailingAnchor constraintEqualToAnchor:tabs.trailingAnchor],
[ownerBtn.widthAnchor constraintEqualToAnchor:tabs.widthAnchor multiplier:0.5],
[ownerBtn.heightAnchor constraintEqualToConstant:40],
[tabs.bottomAnchor constraintEqualToAnchor:summaryBtn.bottomAnchor]
]];
self.tabsBar = tabs;
}
#pragma mark - Representative Card
- (void)setupRepresentativeCard {
......@@ -205,12 +340,32 @@
[self.contentView addSubview:self.buttonSection];
[NSLayoutConstraint activateConstraints:@[
[self.buttonSection.topAnchor constraintEqualToAnchor:self.repCard.bottomAnchor constant:30],
[self.buttonSection.topAnchor constraintEqualToAnchor:self.summaryCard.bottomAnchor constant:30],
[self.buttonSection.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.buttonSection.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.buttonSection.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-40],
[self.buttonSection.heightAnchor constraintEqualToConstant:200], // dynamic later
]];
UIButton *cancelBtn = [UIButton buttonWithType:UIButtonTypeCustom];
cancelBtn.backgroundColor = [UIColor colorWithRed:1.0 green:0.45 blue:0.45 alpha:1];
cancelBtn.layer.cornerRadius = 12;
[cancelBtn setTitle:@"Cancel Appointment" forState:UIControlStateNormal];
cancelBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17];
[cancelBtn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
cancelBtn.translatesAutoresizingMaskIntoConstraints = NO;
[cancelBtn addTarget:self
action:@selector(cancelPressed)
forControlEvents:UIControlEventTouchUpInside];
[self.buttonSection addSubview:cancelBtn];
[NSLayoutConstraint activateConstraints:@[
[cancelBtn.topAnchor constraintEqualToAnchor:self.buttonSection.topAnchor],
[cancelBtn.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[cancelBtn.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[cancelBtn.heightAnchor constraintEqualToConstant:55],
]];
}
#pragma mark - Loading Overlay
......@@ -230,16 +385,6 @@
[spin startAnimating];
}
#pragma mark - Load Data
- (void)loadAppointmentDetails {
self.loadingOverlay.hidden = NO;
// TODO: call your API here
// Then call:
// [self applyAppointmentDataToUI];
}
#pragma mark - Apply Data
- (void)applyAppointmentDataToUI {
......@@ -271,5 +416,14 @@
[self.navigationController popViewControllerAnimated:YES];
}
- (void) cancelPressed {
// [AppointmentAPIClient fetch:self.appointmentID reason:something completion:^(NSDictionary *data, NSError *error) {
// if (error) {
// NSLog(@"Error %@", error.localizedDescription);
// return;
// }
// }];
}
@end
......@@ -43,7 +43,7 @@
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
NSLog(@"📦 Data received by appointment:"
NSLog(@"📦 Data received by appointmentTimeSlot:"
"\n projectId: %@"
"\n projectName: %@"
"\n drawingPlanId: %@"
......@@ -68,8 +68,8 @@
self.selectedStartTime = @"";
[self setupHeader];
[self setupScrollViewAndContent];
[self setupFooter];
[self setupScrollViewAndContent];
[self setupLoadingIndicator];
// If appointmentInfo already provided, consume it
......@@ -261,20 +261,16 @@
#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];
if (self.timeSlot && [self.timeSlot isKindOfClass:[NSArray class]]) {
self.aryTimeSlot = self.timeSlot;
} else {
self.aryTimeSlot = @[];
}
[self rebuildTimeSlotViews];
}
#pragma mark - Time Slot UI (2-column grid)
- (void)rebuildTimeSlotViews {
// Remove previous time slot views (keep first 4: unitLabel, unitLabelName, stepLabel, header)
......@@ -403,6 +399,11 @@
appointVC.slotId = self.selectedSlotID;
appointVC.startDate = self.selectedStartDate;
appointVC.startTime = self.selectedStartTime;
appointVC.projectId = self.projectId;
appointVC.projectName = self.projectName;
appointVC.termsAndCondition = self.termsAndCondition;
appointVC.appointmentType = self.appointmentType;
appointVC.representativeList = self.representativeList;
appointVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:appointVC animated:YES completion:nil];
......
......@@ -462,7 +462,7 @@
} else {
self.representativeList = @[]; // fallback to empty
}
NSLog(@"self.representativeList: %@", self.representativeList);
}];
}
......
......@@ -984,12 +984,11 @@
AppointmentDetailsViewController *vc = [[AppointmentDetailsViewController alloc] init];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
//vc.something = something;
vc.appointmentData = appointmentData;
[self presentViewController:vc animated:YES completion:nil];
}
- (void)handleIssueTap:(UITapGestureRecognizer *)gesture {
UIView *tappedCard = gesture.view;
NSDictionary *issueData = objc_getAssociatedObject(tappedCard, "issueData");
......
......@@ -8,32 +8,32 @@
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment