Commit 9471cbcd authored by Wei Han's avatar Wei Han

code update

parent d4d839ba
......@@ -7,23 +7,23 @@
objects = {
/* Begin PBXBuildFile section */
05F3C0D0AD4C39C0A99776EF /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BD0D0B43375880098BFE1412 /* libPods-example.a */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
6605450219E625E325860241 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A66DA9846FC1AA1735AFC480 /* libPods-example.a */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0DA6F8C368328824431FC7CD /* 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>"; };
01EA15E5BE7E6C0DE0AC746A /* 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>"; };
13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
55BDC75128CC64A879194627 /* 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>"; };
7B95B544AC20B8E9FBAC2BD0 /* 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>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = "<group>"; };
A66DA9846FC1AA1735AFC480 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
BD0D0B43375880098BFE1412 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
......@@ -32,7 +32,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6605450219E625E325860241 /* libPods-example.a in Frameworks */,
05F3C0D0AD4C39C0A99776EF /* libPods-example.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -55,7 +55,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
A66DA9846FC1AA1735AFC480 /* libPods-example.a */,
BD0D0B43375880098BFE1412 /* libPods-example.a */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
0DA6F8C368328824431FC7CD /* Pods-example.debug.xcconfig */,
7B95B544AC20B8E9FBAC2BD0 /* Pods-example.release.xcconfig */,
01EA15E5BE7E6C0DE0AC746A /* Pods-example.debug.xcconfig */,
55BDC75128CC64A879194627 /* Pods-example.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -105,13 +105,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = (
862A4B5823F4E4548FFDB926 /* [CP] Check Pods Manifest.lock */,
CF4B452B2408009DDBC55AC0 /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
D34D31B7F4FB69ABCE829CA8 /* [CP] Embed Pods Frameworks */,
CCE9D6684B3A7D5068209D45 /* [CP] Copy Pods Resources */,
B3A948A2555E0E3C09E2DEF1 /* [CP] Embed Pods Frameworks */,
C8A9149B2DC1DA0C06463423 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -183,29 +183,24 @@
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";
};
862A4B5823F4E4548FFDB926 /* [CP] Check Pods Manifest.lock */ = {
B3A948A2555E0E3C09E2DEF1 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${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-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
CCE9D6684B3A7D5068209D45 /* [CP] Copy Pods Resources */ = {
C8A9149B2DC1DA0C06463423 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -222,21 +217,26 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
D34D31B7F4FB69ABCE829CA8 /* [CP] Embed Pods Frameworks */ = {
CF4B452B2408009DDBC55AC0 /* [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 = 0DA6F8C368328824431FC7CD /* Pods-example.debug.xcconfig */;
baseConfigurationReference = 01EA15E5BE7E6C0DE0AC746A /* Pods-example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......@@ -283,7 +283,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7B95B544AC20B8E9FBAC2BD0 /* Pods-example.release.xcconfig */;
baseConfigurationReference = 55BDC75128CC64A879194627 /* Pods-example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......
......@@ -529,8 +529,9 @@
[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];
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
......
......@@ -363,7 +363,12 @@
[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];
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}]];
[self presentViewController:a animated:YES completion:nil];
}
......
......@@ -517,11 +517,10 @@
}
- (void)handleBackTapped {
if (self.navigationController) {
// If inside a navigation controller → go back
[self.navigationController popViewControllerAnimated:YES];
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
// Otherwise → dismiss modally
[self dismissViewControllerAnimated:YES completion:nil];
}
}
......
......@@ -7,7 +7,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface AppointmentViewMoreViewController : UIViewController
@property (nonatomic, strong) UITableView *agendaTable;
@property (nonatomic, strong) NSArray *agendaList; // appointments for selected day
@property (nonatomic, strong) NSMutableArray *agendaList;
@end
......
......@@ -66,7 +66,6 @@
@end
@interface AppointmentViewMoreViewController () <UITableViewDelegate, UITableViewDataSource, FSCalendarDelegate, FSCalendarDataSource>
@property (nonatomic, strong) UIView *topBar;
......@@ -103,6 +102,9 @@
@property (nonatomic, assign) BOOL blnRefreshCalendar;
@property (nonatomic, strong) FSCalendar *calendar;
@property (nonatomic ,strong) NSLayoutConstraint *calendarHeightConstraint;
@property (nonatomic, strong) UIButton *collapseButton;
@property (nonatomic, assign) BOOL calendarCollapsed;
@end
......@@ -200,6 +202,7 @@
self.filterButton.translatesAutoresizingMaskIntoConstraints = NO;
self.filterButton.tintColor = [UIColor whiteColor];
[self.filterBar addSubview:self.filterButton];
self.calendarCollapsed = YES;
[NSLayoutConstraint activateConstraints:@[
[self.filterBar.topAnchor constraintEqualToAnchor:self.topBar.bottomAnchor],
......@@ -219,54 +222,84 @@
#pragma mark - Calendar
- (void)setupCalendarView {
// 1️⃣ Calendar container
// --- Calendar Container ---
self.calendarContainer = [[UIView alloc] init];
self.calendarContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.calendarContainer];
self.calendarHeightConstraint =
[self.calendarContainer.heightAnchor constraintEqualToConstant:300];
self.calendarHeightConstraint.active = YES;
// 2️⃣ FSCalendar
self.calendar = [[FSCalendar alloc] init];
self.calendar.dataSource = self;
self.calendar.delegate = self;
self.calendar.allowsMultipleSelection = NO;
self.calendar.translatesAutoresizingMaskIntoConstraints = NO;
[self.calendarContainer addSubview:self.calendar];
// 3️⃣ Agenda table below calendar
self.agendaTable = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.agendaTable.delegate = self;
self.agendaTable.dataSource = self;
self.agendaTable.translatesAutoresizingMaskIntoConstraints = NO;
[self.calendarContainer addSubview:self.agendaTable];
[self.agendaTable registerClass:[ListingCell class] forCellReuseIdentifier:@"ListingCell"];
// 4️⃣ Constraints for calendarContainer
[NSLayoutConstraint activateConstraints:@[
[self.calendarContainer.topAnchor constraintEqualToAnchor:self.filterBar.bottomAnchor],
[self.calendarContainer.topAnchor constraintEqualToAnchor:self.filterBar.bottomAnchor constant:8],
[self.calendarContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.calendarContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.calendarContainer.bottomAnchor constraintEqualToAnchor:self.bottomButtonContainer.topAnchor]
]];
// 5️⃣ Constraints for FSCalendar
// --- FSCalendar ---
self.calendar = [[FSCalendar alloc] initWithFrame:CGRectZero];
self.calendar.translatesAutoresizingMaskIntoConstraints = NO;
self.calendar.dataSource = self;
self.calendar.delegate = self;
self.calendar.scope = FSCalendarScopeWeek;
[self.calendarContainer addSubview:self.calendar];
[NSLayoutConstraint activateConstraints:@[
[self.calendar.topAnchor constraintEqualToAnchor:self.calendarContainer.topAnchor],
[self.calendar.leadingAnchor constraintEqualToAnchor:self.calendarContainer.leadingAnchor],
[self.calendar.trailingAnchor constraintEqualToAnchor:self.calendarContainer.trailingAnchor],
[self.calendar.heightAnchor constraintEqualToConstant:320] // fixed calendar height
[self.calendar.bottomAnchor constraintEqualToAnchor:self.calendarContainer.bottomAnchor],
]];
// --- Collapse / Expand Button ---
self.collapseButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.collapseButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.collapseButton setTitle:@"Expand Calendar ▼" forState:UIControlStateNormal];
self.collapseButton.titleLabel.font = [UIFont boldSystemFontOfSize:14];
[self.collapseButton addTarget:self
action:@selector(onToggleCalendar)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.collapseButton];
// --- Button Constraints ---
[NSLayoutConstraint activateConstraints:@[
[self.collapseButton.topAnchor constraintEqualToAnchor:self.calendarContainer.bottomAnchor constant:8],
[self.collapseButton.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.collapseButton.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.collapseButton.heightAnchor constraintEqualToConstant:30],
]];
// --- Agenda Table ---
self.agendaTable = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.agendaTable.delegate = self;
self.agendaTable.dataSource = self;
self.agendaTable.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.agendaTable];
[self.agendaTable registerClass:[ListingCell class] forCellReuseIdentifier:@"ListingCell"];
self.agendaTable.estimatedRowHeight = 60;
self.agendaTable.rowHeight = UITableViewAutomaticDimension;
// Pull-to-refresh
UIRefreshControl *refresh = [[UIRefreshControl alloc] init];
[refresh addTarget:self action:@selector(onRefresh) forControlEvents:UIControlEventValueChanged];
if (@available(iOS 10.0, *)) {
self.agendaTable.refreshControl = refresh;
} else {
[self.agendaTable addSubview:refresh];
}
// 6️⃣ Constraints for agendaTable
[NSLayoutConstraint activateConstraints:@[
[self.agendaTable.topAnchor constraintEqualToAnchor:self.calendar.bottomAnchor],
[self.agendaTable.leadingAnchor constraintEqualToAnchor:self.calendarContainer.leadingAnchor],
[self.agendaTable.trailingAnchor constraintEqualToAnchor:self.calendarContainer.trailingAnchor],
[self.agendaTable.bottomAnchor constraintEqualToAnchor:self.calendarContainer.bottomAnchor]
[self.agendaTable.topAnchor constraintEqualToAnchor:self.collapseButton.bottomAnchor constant:8],
[self.agendaTable.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.agendaTable.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.agendaTable.bottomAnchor constraintEqualToAnchor:self.bottomButtonContainer.topAnchor constant:-8],
]];
}
#pragma mark - FSCalendarDelegate
- (void)calendar:(FSCalendar *)calendar didSelectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position {
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
df.dateFormat = @"dd MMM yyyy";
......@@ -312,16 +345,31 @@
return events.count; // number of dots to show
}
- (void)calendar:(FSCalendar *)calendar boundingRectWillChange:(CGRect)bounds animated:(BOOL)animated {
self.calendarHeightConstraint.constant = bounds.size.height;
[self.view layoutIfNeeded];
}
- (void)onToggleCalendar {
self.calendarCollapsed = !self.calendarCollapsed;
FSCalendarScope newScope = self.calendarCollapsed ? FSCalendarScopeWeek : FSCalendarScopeMonth;
[self.calendar setScope:newScope animated:YES];
NSString *title = self.calendarCollapsed ? @"Expand Calendar ▼" : @"Collapse Calendar ▲";
[self.collapseButton setTitle:title forState:UIControlStateNormal];
}
#pragma mark - Listing Container
- (void)setupListingView {
// 1️⃣ Container for listing
// Container for listing
self.listingContainer = [[UIView alloc] init];
self.listingContainer.backgroundColor = UIColor.whiteColor;
self.listingContainer.translatesAutoresizingMaskIntoConstraints = NO;
self.listingContainer.hidden = YES;
[self.view addSubview:self.listingContainer];
// 2️⃣ Listing table
// Listing table
self.listingTable = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
self.listingTable.delegate = self;
self.listingTable.dataSource = self;
......@@ -331,7 +379,7 @@
// ✅ Register cell class **before any dequeuing**
[self.listingTable registerClass:[ListingCell class] forCellReuseIdentifier:@"ListingCell"];
// 3️⃣ Container constraints
// Container constraints
[NSLayoutConstraint activateConstraints:@[
[self.listingContainer.topAnchor constraintEqualToAnchor:self.filterBar.bottomAnchor],
[self.listingContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
......@@ -339,24 +387,15 @@
[self.listingContainer.bottomAnchor constraintEqualToAnchor:self.bottomButtonContainer.topAnchor]
]];
// 4️⃣ Table fills container
// Table fills container
[NSLayoutConstraint activateConstraints:@[
[self.listingTable.topAnchor constraintEqualToAnchor:self.listingContainer.topAnchor],
[self.listingTable.topAnchor constraintEqualToAnchor:self.listingContainer.topAnchor constant:8],
[self.listingTable.leadingAnchor constraintEqualToAnchor:self.listingContainer.leadingAnchor],
[self.listingTable.trailingAnchor constraintEqualToAnchor:self.listingContainer.trailingAnchor],
[self.listingTable.bottomAnchor constraintEqualToAnchor:self.listingContainer.bottomAnchor]
]];
// 5️⃣ Pull-to-refresh
UIRefreshControl *refresh = [[UIRefreshControl alloc] init];
[refresh addTarget:self action:@selector(onRefresh) forControlEvents:UIControlEventValueChanged];
if (@available(iOS 10.0, *)) {
self.listingTable.refreshControl = refresh;
} else {
[self.listingTable addSubview:refresh];
}
// 6️⃣ Ensure automatic row height (optional)
// Ensure automatic row height (optional)
self.listingTable.estimatedRowHeight = 60;
self.listingTable.rowHeight = UITableViewAutomaticDimension;
}
......@@ -436,9 +475,13 @@
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *item = tableView == self.agendaTable ? self.agendaList[indexPath.row]
: self.arrayListingSection[indexPath.section][@"data"][indexPath.row];
[self clickListingItemWithItem:item];
if (tableView == self.listingTable) {
NSDictionary *item = self.arrayListing[indexPath.row];
[self clickListingItemWithItem:item];
} else if (tableView == self.agendaTable) {
NSDictionary *item = self.agendaList[indexPath.row];
[self clickListingItemWithItem:item];
}
}
- (void)clickListingItemWithItem:(NSDictionary *)item {
......@@ -449,7 +492,7 @@
}
- (void)onRefresh {
[self fetchDashboardInfo];
[self fetchDashboardInfo];
}
#pragma mark - Bottom Button
......@@ -595,7 +638,7 @@
#pragma mark - Actions
- (void)onBack {
[self.navigationController popViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)onToggleTab {
......@@ -687,192 +730,192 @@
}
- (void)processAppointmentData:(NSArray *)data {
if (!data) {
[self.arrayListing removeAllObjects];
[self.arrayListingSection removeAllObjects];
[self.listingTable reloadData];
return;
}
// 1. Filter by type and time
NSMutableArray *filtered = [NSMutableArray array];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
df.dateFormat = @"dd MMM yyyy hh:mm a"; // matches RN format 'DD MMM YYYY hh:mm a'
for (NSDictionary *item in data) {
NSString *apptType = item[@"appointment_type"] ?: @"";
NSString *startDate = item[@"start_date"] ?: @""; // 'DD MMM YYYY'
NSString *startTime = item[@"start_time"] ?: @""; // 'hh:mm a'
// combine and parse
NSString *combined = [NSString stringWithFormat:@"%@ %@", startDate, startTime];
NSDate *startDateTime = [df dateFromString:combined];
if (!startDateTime) continue;
// determine upcoming vs history
BOOL isHistory = [startDateTime timeIntervalSinceNow] < 0;
// time filter
BOOL timePass = YES;
if ([self.apptFilterTime isEqualToString:@"History"]) {
timePass = isHistory;
} else if ([self.apptFilterTime isEqualToString:@"Upcoming"]) {
timePass = !isHistory;
}
// type filter
BOOL typePass = YES;
if (![self.apptFilterType isEqualToString:@"All"]) {
typePass = [apptType isEqualToString:self.apptFilterType];
}
if (timePass && typePass) {
[filtered addObject:item];
}
}
// 2. Sort
[filtered sortUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
NSString *aCombined = [NSString stringWithFormat:@"%@ %@", a[@"start_date"], a[@"start_time"]];
NSString *bCombined = [NSString stringWithFormat:@"%@ %@", b[@"start_date"], b[@"start_time"]];
NSDate *aDate = [df dateFromString:aCombined];
NSDate *bDate = [df dateFromString:bCombined];
if (!aDate || !bDate) return NSOrderedSame;
if ([self.apptFilterTime isEqualToString:@"Upcoming"]) {
return [aDate compare:bDate];
} else {
return [bDate compare:aDate];
}
}];
// 3. Build sections grouped by start_date
if (!data) {
[self.arrayListing removeAllObjects];
[self.arrayListing addObjectsFromArray:filtered];
[self.arrayListingSection removeAllObjects];
NSMutableDictionary *sectionsDict = [NSMutableDictionary dictionary];
for (NSDictionary *item in filtered) {
NSString *dateKey = item[@"start_date"] ?: @"";
if (!dateKey) continue;
if (!sectionsDict[dateKey]) sectionsDict[dateKey] = [NSMutableArray array];
[sectionsDict[dateKey] addObject:item];
}
// convert to array preserving order of filtered array
NSMutableSet *seenDates = [NSMutableSet set];
for (NSDictionary *item in filtered) {
NSString *dateKey = item[@"start_date"];
if (![seenDates containsObject:dateKey]) {
[seenDates addObject:dateKey];
NSArray *itemsForDate = sectionsDict[dateKey];
NSDictionary *section = @{@"title": dateKey, @"data": itemsForDate ?: @[]};
[self.arrayListingSection addObject:section];
}
[self.listingTable reloadData];
return;
}
// 1. Filter by type and time
NSMutableArray *filtered = [NSMutableArray array];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
df.dateFormat = @"dd MMM yyyy hh:mm a"; // matches RN format 'DD MMM YYYY hh:mm a'
for (NSDictionary *item in data) {
NSString *apptType = item[@"appointment_type"] ?: @"";
NSString *startDate = item[@"start_date"] ?: @""; // 'DD MMM YYYY'
NSString *startTime = item[@"start_time"] ?: @""; // 'hh:mm a'
// combine and parse
NSString *combined = [NSString stringWithFormat:@"%@ %@", startDate, startTime];
NSDate *startDateTime = [df dateFromString:combined];
if (!startDateTime) continue;
// determine upcoming vs history
BOOL isHistory = [startDateTime timeIntervalSinceNow] < 0;
// time filter
BOOL timePass = YES;
if ([self.apptFilterTime isEqualToString:@"History"]) {
timePass = isHistory;
} else if ([self.apptFilterTime isEqualToString:@"Upcoming"]) {
timePass = !isHistory;
}
// 4. Build dots map (date -> array of {type,color})
[self.aryDotsByDate removeAllObjects];
for (NSDictionary *item in self.arrayListing) {
NSString *strConvertDate = item[@"start_date"]; // 'DD MMM YYYY'
// convert to yyyy-MM-dd key
NSDateFormatter *dfIn = [[NSDateFormatter alloc] init];
dfIn.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dfIn.dateFormat = @"dd MMM yyyy";
NSDate *d = [dfIn dateFromString:strConvertDate];
NSDateFormatter *dfOut = [[NSDateFormatter alloc] init];
dfOut.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dfOut.dateFormat = @"yyyy-MM-dd";
NSString *key = [dfOut stringFromDate:d] ?: strConvertDate;
NSString *type = item[@"appointment_type"] ?: @"";
UIColor *color = [type isEqualToString:@"Handover"] ? [self colorFromHex:@"#047C74"] : [self colorFromHex:@"#C84132"];
NSMutableArray *dots = self.aryDotsByDate[key];
if (!dots) {
dots = [NSMutableArray array];
self.aryDotsByDate[key] = dots;
}
[dots addObject:@{@"type": type, @"color": color}];
// type filter
BOOL typePass = YES;
if (![self.apptFilterType isEqualToString:@"All"]) {
typePass = [apptType isEqualToString:self.apptFilterType];
}
// 5. refresh table + calendar
[self.listingTable reloadData];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
if (timePass && typePass) {
[filtered addObject:item];
}
}
// 2. Sort
[filtered sortUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
NSString *aCombined = [NSString stringWithFormat:@"%@ %@", a[@"start_date"], a[@"start_time"]];
NSString *bCombined = [NSString stringWithFormat:@"%@ %@", b[@"start_date"], b[@"start_time"]];
NSDate *aDate = [df dateFromString:aCombined];
NSDate *bDate = [df dateFromString:bCombined];
if (!aDate || !bDate) return NSOrderedSame;
if ([self.apptFilterTime isEqualToString:@"Upcoming"]) {
return [aDate compare:bDate];
} else {
return [bDate compare:aDate];
}
}];
// 3. Build sections grouped by start_date
[self.arrayListing removeAllObjects];
[self.arrayListing addObjectsFromArray:filtered];
[self.arrayListingSection removeAllObjects];
NSMutableDictionary *sectionsDict = [NSMutableDictionary dictionary];
for (NSDictionary *item in filtered) {
NSString *dateKey = item[@"start_date"] ?: @"";
if (!dateKey) continue;
if (!sectionsDict[dateKey]) sectionsDict[dateKey] = [NSMutableArray array];
[sectionsDict[dateKey] addObject:item];
}
// convert to array preserving order of filtered array
NSMutableSet *seenDates = [NSMutableSet set];
for (NSDictionary *item in filtered) {
NSString *dateKey = item[@"start_date"];
if (![seenDates containsObject:dateKey]) {
[seenDates addObject:dateKey];
NSArray *itemsForDate = sectionsDict[dateKey];
NSDictionary *section = @{@"title": dateKey, @"data": itemsForDate ?: @[]};
[self.arrayListingSection addObject:section];
}
}
// 4. Build dots map (date -> array of {type,color})
[self.aryDotsByDate removeAllObjects];
for (NSDictionary *item in self.arrayListing) {
NSString *strConvertDate = item[@"start_date"]; // 'DD MMM YYYY'
// convert to yyyy-MM-dd key
NSDateFormatter *dfIn = [[NSDateFormatter alloc] init];
dfIn.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dfIn.dateFormat = @"dd MMM yyyy";
NSDate *d = [dfIn dateFromString:strConvertDate];
NSDateFormatter *dfOut = [[NSDateFormatter alloc] init];
dfOut.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dfOut.dateFormat = @"yyyy-MM-dd";
for (NSDictionary *item in self.arrayListing) {
NSString *startDateStr = item[@"start_date"]; // "27 Nov 2025"
NSDate *d = [dfIn dateFromString:startDateStr];
if (!d) continue;
NSString *key = [dfOut stringFromDate:d];
// Status color logic
NSString *apptType = item[@"appointment_type"];
UIColor *c = [apptType isEqualToString:@"Handover"]
? [self colorFromHex:@"#047C74"]
: [self colorFromHex:@"#C84132"];
if (!dict[key]) dict[key] = [NSMutableArray array];
[dict[key] addObject:@{ @"color": c }];
NSString *key = [dfOut stringFromDate:d] ?: strConvertDate;
NSString *type = item[@"appointment_type"] ?: @"";
UIColor *color = [type isEqualToString:@"Handover"] ? [self colorFromHex:@"#047C74"] : [self colorFromHex:@"#C84132"];
NSMutableArray *dots = self.aryDotsByDate[key];
if (!dots) {
dots = [NSMutableArray array];
self.aryDotsByDate[key] = dots;
}
self.aryDotsByDate = dict;
[self.calendar reloadData];
self.blnRefreshCalendar = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.blnRefreshCalendar = NO;
});
[dots addObject:@{@"type": type, @"color": color}];
}
self.filterLabel.text = [NSString stringWithFormat:@"Filter by %@ (%@)",
self.apptFilterType,
self.apptFilterTime];
// 5. refresh table + calendar
[self.listingTable reloadData];
// Auto-select today or first date that has appointments
NSDateFormatter *dfToday = [[NSDateFormatter alloc] init];
dfToday.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dfToday.dateFormat = @"dd MMM yyyy";
NSString *todayString = [dfToday stringFromDate:[NSDate date]];
df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
df.dateFormat = @"dd MMM yyyy";
NSMutableArray *todayList = [NSMutableArray array];
for (NSDictionary *item in self.arrayListing) {
if ([item[@"start_date"] isEqualToString:todayString]) {
[todayList addObject:item];
}
}
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSDateFormatter *dfIn = [[NSDateFormatter alloc] init];
dfIn.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dfIn.dateFormat = @"dd MMM yyyy";
NSDateFormatter *dfOut = [[NSDateFormatter alloc] init];
dfOut.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dfOut.dateFormat = @"yyyy-MM-dd";
for (NSDictionary *item in self.arrayListing) {
NSString *startDateStr = item[@"start_date"]; // "27 Nov 2025"
NSDate *d = [dfIn dateFromString:startDateStr];
if (!d) continue;
NSString *key = [dfOut stringFromDate:d];
if (todayList.count > 0) {
[self.calendar selectDate:[NSDate date] scrollToDate:YES];
self.agendaList = todayList;
} else if (self.arrayListing.count > 0) {
NSDictionary *firstItem = self.arrayListing[0];
NSDate *firstDate = [df dateFromString:firstItem[@"start_date"]];
if (firstDate) {
[self.calendar selectDate:firstDate scrollToDate:YES];
// populate agendaList for firstDate
NSMutableArray *firstDateList = [NSMutableArray array];
for (NSDictionary *item in self.arrayListing) {
if ([item[@"start_date"] isEqualToString:firstItem[@"start_date"]]) {
[firstDateList addObject:item];
}
}
self.agendaList = firstDateList;
// Status color logic
NSString *apptType = item[@"appointment_type"];
UIColor *c = [apptType isEqualToString:@"Handover"]
? [self colorFromHex:@"#047C74"]
: [self colorFromHex:@"#C84132"];
if (!dict[key]) dict[key] = [NSMutableArray array];
[dict[key] addObject:@{ @"color": c }];
}
self.aryDotsByDate = dict;
[self.calendar reloadData];
self.blnRefreshCalendar = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.blnRefreshCalendar = NO;
});
self.filterLabel.text = [NSString stringWithFormat:@"Filter by %@ (%@)",
self.apptFilterType,
self.apptFilterTime];
// Auto-select today or first date that has appointments
NSDateFormatter *dfToday = [[NSDateFormatter alloc] init];
dfToday.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
dfToday.dateFormat = @"dd MMM yyyy";
NSString *todayString = [dfToday stringFromDate:[NSDate date]];
df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
df.dateFormat = @"dd MMM yyyy";
NSMutableArray *todayList = [NSMutableArray array];
for (NSDictionary *item in self.arrayListing) {
if ([item[@"start_date"] isEqualToString:todayString]) {
[todayList addObject:item];
}
}
if (todayList.count > 0) {
[self.calendar selectDate:[NSDate date] scrollToDate:YES];
self.agendaList = todayList;
} else if (self.arrayListing.count > 0) {
NSDictionary *firstItem = self.arrayListing[0];
NSDate *firstDate = [df dateFromString:firstItem[@"start_date"]];
if (firstDate) {
[self.calendar selectDate:firstDate scrollToDate:YES];
// populate agendaList for firstDate
NSMutableArray *firstDateList = [NSMutableArray array];
for (NSDictionary *item in self.arrayListing) {
if ([item[@"start_date"] isEqualToString:firstItem[@"start_date"]]) {
[firstDateList addObject:item];
}
}
self.agendaList = firstDateList;
}
}
[self.agendaTable reloadData];
}
......
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