Commit 81ec60ad authored by Wei Han's avatar Wei Han

code update

parent 9471cbcd
......@@ -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 */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
EEFB98A2480FDDBFC790E56F /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 574BB54B207C088196A9FBBB /* libPods-example.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
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>"; };
574BB54B207C088196A9FBBB /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
BD0D0B43375880098BFE1412 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
85CE699693EE18A8E1C24E42 /* 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>"; };
B696EC4DAEE5EDE78A1EEB18 /* 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>"; };
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 = (
05F3C0D0AD4C39C0A99776EF /* libPods-example.a in Frameworks */,
EEFB98A2480FDDBFC790E56F /* libPods-example.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -55,7 +55,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
BD0D0B43375880098BFE1412 /* libPods-example.a */,
574BB54B207C088196A9FBBB /* libPods-example.a */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
01EA15E5BE7E6C0DE0AC746A /* Pods-example.debug.xcconfig */,
55BDC75128CC64A879194627 /* Pods-example.release.xcconfig */,
85CE699693EE18A8E1C24E42 /* Pods-example.debug.xcconfig */,
B696EC4DAEE5EDE78A1EEB18 /* Pods-example.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -105,13 +105,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = (
CF4B452B2408009DDBC55AC0 /* [CP] Check Pods Manifest.lock */,
C87E7C1B2ADF35B0CBBA26BF /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
B3A948A2555E0E3C09E2DEF1 /* [CP] Embed Pods Frameworks */,
C8A9149B2DC1DA0C06463423 /* [CP] Copy Pods Resources */,
DE4DC932E04B9540533EF987 /* [CP] Embed Pods Frameworks */,
9DEA634DDB892A12B25520B4 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -183,24 +183,7 @@
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
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",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${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-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
C8A9149B2DC1DA0C06463423 /* [CP] Copy Pods Resources */ = {
9DEA634DDB892A12B25520B4 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -217,7 +200,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
CF4B452B2408009DDBC55AC0 /* [CP] Check Pods Manifest.lock */ = {
C87E7C1B2ADF35B0CBBA26BF /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -239,6 +222,23 @@
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;
};
DE4DC932E04B9540533EF987 /* [CP] Embed Pods Frameworks */ = {
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";
outputFileListPaths = (
"${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-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
......@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 01EA15E5BE7E6C0DE0AC746A /* Pods-example.debug.xcconfig */;
baseConfigurationReference = 85CE699693EE18A8E1C24E42 /* Pods-example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......@@ -283,7 +283,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 55BDC75128CC64A879194627 /* Pods-example.release.xcconfig */;
baseConfigurationReference = B696EC4DAEE5EDE78A1EEB18 /* Pods-example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......
......@@ -105,6 +105,9 @@
@property (nonatomic ,strong) NSLayoutConstraint *calendarHeightConstraint;
@property (nonatomic, strong) UIButton *collapseButton;
@property (nonatomic, assign) BOOL calendarCollapsed;
@property (nonatomic, strong) NSArray<NSDictionary *> *agendaSections;
@property (nonatomic, strong) NSDate *selectedAgendaStartDate;
@property (nonatomic, strong) NSDateFormatter *stringDF;
@end
......@@ -124,6 +127,10 @@
self.showingCalendar = YES;
self.stringDF = [[NSDateFormatter alloc] init];
self.stringDF.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
self.stringDF.dateFormat = @"dd MMM yyyy";
[self setupTopBar];
[self setupFilterBar];
[self setupBottomButton];
......@@ -220,6 +227,46 @@
]];
}
#pragma mark - Listing Container
- (void)setupListingView {
// 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];
// Listing table
self.listingTable = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
self.listingTable.delegate = self;
self.listingTable.dataSource = self;
self.listingTable.translatesAutoresizingMaskIntoConstraints = NO;
[self.listingContainer addSubview:self.listingTable];
// ✅ Register cell class **before any dequeuing**
[self.listingTable registerClass:[ListingCell class] forCellReuseIdentifier:@"ListingCell"];
// Container constraints
[NSLayoutConstraint activateConstraints:@[
[self.listingContainer.topAnchor constraintEqualToAnchor:self.filterBar.bottomAnchor],
[self.listingContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.listingContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.listingContainer.bottomAnchor constraintEqualToAnchor:self.bottomButtonContainer.topAnchor]
]];
// Table fills container
[NSLayoutConstraint activateConstraints:@[
[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]
]];
// Ensure automatic row height (optional)
self.listingTable.estimatedRowHeight = 60;
self.listingTable.rowHeight = UITableViewAutomaticDimension;
}
#pragma mark - Calendar
- (void)setupCalendarView {
// --- Calendar Container ---
......@@ -282,13 +329,13 @@
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];
}
// 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];
// }
[NSLayoutConstraint activateConstraints:@[
[self.agendaTable.topAnchor constraintEqualToAnchor:self.collapseButton.bottomAnchor constant:8],
......@@ -300,22 +347,28 @@
#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";
NSString *selectedDate = [df stringFromDate:date];
NSMutableArray *list = [NSMutableArray array];
// Save selected date
self.selectedAgendaStartDate = [[NSCalendar currentCalendar] startOfDayForDate:date];
for (NSDictionary *item in self.arrayListing) {
if ([item[@"start_date"] isEqualToString:selectedDate]) {
[list addObject:item];
// Rebuild the agenda so that selected date becomes the start
[self prepareAgendaSections];
// Scroll to the section for this date
NSString *selectedDateString = [self.stringDF stringFromDate:self.selectedAgendaStartDate];
NSInteger sectionIndex = NSNotFound;
for (NSInteger i = 0; i < self.agendaSections.count; i++) {
if ([self.agendaSections[i][@"date"] isEqualToString:selectedDateString]) {
sectionIndex = i;
break;
}
}
self.agendaList = list;
[self.agendaTable reloadData];
if (sectionIndex != NSNotFound) {
[self.agendaTable scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:sectionIndex]
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
}
}
// If your FSCalendar supports eventColorsForDate:
......@@ -360,77 +413,67 @@
[self.collapseButton setTitle:title forState:UIControlStateNormal];
}
#pragma mark - Listing Container
- (void)setupListingView {
// 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];
// Listing table
self.listingTable = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
self.listingTable.delegate = self;
self.listingTable.dataSource = self;
self.listingTable.translatesAutoresizingMaskIntoConstraints = NO;
[self.listingContainer addSubview:self.listingTable];
// ✅ Register cell class **before any dequeuing**
[self.listingTable registerClass:[ListingCell class] forCellReuseIdentifier:@"ListingCell"];
// Container constraints
[NSLayoutConstraint activateConstraints:@[
[self.listingContainer.topAnchor constraintEqualToAnchor:self.filterBar.bottomAnchor],
[self.listingContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.listingContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.listingContainer.bottomAnchor constraintEqualToAnchor:self.bottomButtonContainer.topAnchor]
]];
// Table fills container
[NSLayoutConstraint activateConstraints:@[
[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]
]];
// Ensure automatic row height (optional)
self.listingTable.estimatedRowHeight = 60;
self.listingTable.rowHeight = UITableViewAutomaticDimension;
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (tableView == self.agendaTable) return 1;
if (tableView == self.agendaTable) {
return self.agendaSections.count;
}
return self.arrayListingSection.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.agendaTable) return self.agendaList.count;
if (tableView == self.agendaTable) {
NSDictionary *sec = self.agendaSections[section];
NSArray *items = sec[@"items"];
return (items.count > 0) ? items.count : 1; // always 1 for empty section
}
NSDictionary *sec = self.arrayListingSection[section];
return [sec[@"data"] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ✅ Always dequeue after registering
ListingCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ListingCell" forIndexPath:indexPath];
NSDictionary *item;
if (tableView == self.agendaTable) {
item = self.agendaList[indexPath.row];
NSDictionary *sec = self.agendaSections[indexPath.section];
NSArray *items = sec[@"items"];
if (items.count == 0) {
// Empty section cell
cell.statusLabel.text = @"No Appointments";
cell.timeBadge.text = @"";
cell.timeBadge.backgroundColor = UIColor.clearColor;
cell.unitOwnerLabel.text = @"";
} else {
NSDictionary *sec = self.arrayListingSection[indexPath.section];
item = sec[@"data"][indexPath.row];
NSDictionary *item = items[indexPath.row];
NSString *status = item[@"status"] ?: @"";
NSString *apptType = item[@"appointment_type"] ?: @"";
NSString *startTime = item[@"start_time"] ?: @"";
cell.statusLabel.text = [status isEqualToString:@"Cancelled"] ? status : apptType;
cell.timeBadge.text = startTime;
cell.timeBadge.backgroundColor = [apptType isEqualToString:@"Handover"]
? [self colorFromHex:@"#047C74"]
: [self colorFromHex:@"#C84132"];
cell.timeBadge.textColor = UIColor.whiteColor;
NSArray *primary = item[@"primary_buyer"];
NSArray *joint = item[@"joint_buyer"];
NSString *owner = (primary.count > 0) ? primary[0][@"name"]
: (joint.count > 0 ? joint[0][@"name"] : @"");
cell.unitOwnerLabel.text = [NSString stringWithFormat:@"%@ (%@)", item[@"unit_no"], owner];
}
} else {
// normal listing table config
NSDictionary *sec = self.arrayListingSection[indexPath.section];
NSDictionary *item = sec[@"data"][indexPath.row];
// Configure cell
NSString *status = item[@"status"] ?: @"";
NSString *apptType = item[@"appointment_type"] ?: @"";
NSString *startTime = item[@"start_time"] ?: @"";
cell.statusLabel.text = [status isEqualToString:@"Cancelled"] ? status : apptType;
cell.timeBadge.text = startTime;
cell.timeBadge.backgroundColor = [apptType isEqualToString:@"Handover"]
? [self colorFromHex:@"#047C74"]
......@@ -443,25 +486,52 @@
: (joint.count > 0 ? joint[0][@"name"] : @"");
cell.unitOwnerLabel.text = [NSString stringWithFormat:@"%@ (%@)", item[@"unit_no"], owner];
}
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 60; // fixed height
if (tableView == self.agendaTable) {
NSDictionary *sec = self.agendaSections[indexPath.section];
NSArray *items = sec[@"items"];
if (items.count == 0) return 0.1; // empty cell height
return 60; // normal cell height
}
return 60;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return tableView == self.agendaTable ? 0.1 : 30;
return 30;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.1;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (tableView == self.agendaTable) {
NSDictionary *sec = self.agendaSections[section];
return sec[@"date"];
}
return @"1";
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (tableView == self.agendaTable) return [UIView new];
if (tableView == self.agendaTable) {
NSDictionary *sec = self.agendaSections[section];
NSString *dateString = sec[@"date"];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, tableView.bounds.size.width - 32, 30)];
lbl.text = dateString;
lbl.font = [UIFont boldSystemFontOfSize:14];
lbl.textColor = UIColor.darkGrayColor;
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, 30)];
v.backgroundColor = UIColor.whiteColor;
[v addSubview:lbl];
return v;
}
NSDictionary *sec = self.arrayListingSection[section];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, tableView.bounds.size.width - 32, 30)];
......@@ -475,13 +545,17 @@
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
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];
NSDictionary *item;
if (tableView == self.agendaTable) {
NSDictionary *sec = self.agendaSections[indexPath.section];
NSArray *items = sec[@"items"];
if (items.count == 0) return; // nothing to select
item = items[indexPath.row];
} else {
NSDictionary *sec = self.arrayListingSection[indexPath.section];
item = sec[@"data"][indexPath.row];
}
[self clickListingItemWithItem:item];
}
- (void)clickListingItemWithItem:(NSDictionary *)item {
......@@ -491,6 +565,119 @@
[self presentViewController:vc animated:YES completion:nil];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView != self.agendaTable) return;
// Get visible rows
NSArray<NSIndexPath *> *visible = [self.agendaTable indexPathsForVisibleRows];
if (visible.count == 0) return;
// Find the *topmost* row (smallest section + row)
NSIndexPath *topIndex = visible.firstObject;
for (NSIndexPath *ip in visible) {
if (ip.section < topIndex.section ||
(ip.section == topIndex.section && ip.row < topIndex.row)) {
topIndex = ip;
}
}
// Get date from section
NSDictionary *sectionInfo = self.agendaSections[topIndex.section];
NSString *dateStr = sectionInfo[@"date"];
if (!dateStr) return;
// Convert to NSDate
NSDate *date = [self.stringDF dateFromString:dateStr];
if (!date) return;
// Avoid infinite loop when we update calendar from user taps
if ([self.calendar.selectedDate isEqualToDate:date]) return;
// Update calendar selection
[self.calendar selectDate:date scrollToDate:YES];
}
- (void)prepareAgendaSections {
if (!self.arrayListing || self.arrayListing.count == 0) {
// still show at least today
NSDate *today = [[NSCalendar currentCalendar] startOfDayForDate:[NSDate date]];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
df.dateFormat = @"dd MMM yyyy";
NSString *todayStr = [df stringFromDate:today];
self.agendaSections = @[ @{ @"date": todayStr, @"items": @[] } ];
[self.agendaTable reloadData];
return;
}
// 1️⃣ Map: date string -> appointments
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
df.dateFormat = @"dd MMM yyyy";
for (NSDictionary *item in self.arrayListing) {
NSString *dateStr = item[@"start_date"];
if (!dateStr) continue;
if (!dict[dateStr]) dict[dateStr] = [NSMutableArray array];
[dict[dateStr] addObject:item];
}
// 2️⃣ Sort all appointment dates
NSArray *allDates = [[dict allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
NSDate *da = [df dateFromString:a];
NSDate *db = [df dateFromString:b];
return [da compare:db];
}];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *today = [calendar startOfDayForDate:[NSDate date]];
// If no appointment dates exist (edge case)
NSDate *firstAppt = (allDates.count > 0) ? [df dateFromString:allDates.firstObject] : today;
NSDate *lastAppt = (allDates.count > 0) ? [df dateFromString:allDates.lastObject] : today;
// 3️⃣ Expand date range like RN Agenda
// Start at the earlier of today or first appointment
NSDate *startDate;
// If user selected a date, start from there
if (self.selectedAgendaStartDate) {
startDate = self.selectedAgendaStartDate;
} else {
// default behavior (today or firstAppt)
startDate = [firstAppt compare:today] == NSOrderedAscending ? firstAppt : today;
}
// Add a forward buffer (e.g. 60 days)
NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:60 toDate:lastAppt options:0];
// 4️⃣ Build all sections
NSMutableArray *sections = [NSMutableArray array];
NSDate *currentDate = startDate;
while ([currentDate compare:endDate] != NSOrderedDescending) {
NSString *dateStr = [df stringFromDate:currentDate];
NSArray *itemsForDate = dict[dateStr] ?: @[];
[sections addObject:@{
@"date": dateStr,
@"items": itemsForDate
}];
currentDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1
toDate:currentDate
options:0];
}
self.agendaSections = sections;
[self.agendaTable reloadData];
}
- (void)onRefresh {
[self fetchDashboardInfo];
}
......@@ -526,51 +713,88 @@
#pragma mark - Filter Overlay
- (void)setupFilterOverlay {
self.filterOverlay = [[UIView alloc] initWithFrame:self.view.bounds];
self.filterOverlay.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8];
self.filterOverlay.hidden = YES;
self.filterOverlay.userInteractionEnabled = NO;
CGFloat y = 120;
// 1️⃣ Dim background
UIView *dimView = [[UIView alloc] initWithFrame:self.filterOverlay.bounds];
dimView.backgroundColor = [UIColor whiteColor];
[self.filterOverlay addSubview:dimView];
// Tap anywhere to close
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(hideFilterOverlay)];
[dimView addGestureRecognizer:tap];
// 2️⃣ Content container (MUST BE ABOVE dimView)
UIView *contentView = [[UIView alloc] initWithFrame:self.filterOverlay.bounds];
contentView.backgroundColor = UIColor.clearColor; // content is transparent
[self.filterOverlay addSubview:contentView];
// -----------------------------
// ADD YOUR FILTER BUTTONS HERE
// -----------------------------
CGFloat y = 100;
// 1️⃣ Appointment Type Header
[self createFilterHeaderWithText:@"Appointment Type" y:y parent:contentView];
y += 40;
// 2️⃣ Type options
NSArray *typeTitles = @[@"All", @"Handover", @"Joint Inspection", @"Rectification"];
for (int i=0; i<typeTitles.count; i++) {
UIButton *b = [UIButton buttonWithType:UIButtonTypeCustom];
b.frame = CGRectMake(0, y + i*50, self.view.bounds.size.width, 50);
[b setTitle:typeTitles[i] forState:UIControlStateNormal];
[b addTarget:self action:@selector(onFilterTypeTap:) forControlEvents:UIControlEventTouchUpInside];
b.tag = i;
[self.filterOverlay addSubview:b];
UIImageView *radio = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.bounds.size.width - 40, y + i*50 + 12, 24, 24)];
radio.tag = 100 + i;
for (int i = 0; i < typeTitles.count; i++) {
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, y, contentView.bounds.size.width, 50)];
btn.tag = i;
[btn addTarget:self action:@selector(onFilterTypeTap:) forControlEvents:UIControlEventTouchUpInside];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, btn.bounds.size.width - 60, 50)];
label.text = typeTitles[i];
label.font = [UIFont systemFontOfSize:16];
label.textColor = [UIColor blackColor];
[btn addSubview:label];
UIImageView *radio = [[UIImageView alloc] initWithFrame:CGRectMake(btn.bounds.size.width - 40, 13, 24, 24)];
radio.tag = 100 + i; // so you can update later in refreshFilterOverlay
radio.image = [self.apptFilterType isEqualToString:typeTitles[i]] ? [UIImage imageNamed:@"radio_check"] : [UIImage imageNamed:@"radio_uncheck"];
[self.filterOverlay addSubview:radio];
[btn addSubview:radio];
[contentView addSubview:btn];
y += 50;
}
// Time filter section
CGFloat timeStartY = y + typeTitles.count * 50 + 20;
// Space before next header
y += 20;
// 3️⃣ Appointment State Header
[self createFilterHeaderWithText:@"Appointment State" y:y parent:contentView];
y += 40;
// 4️⃣ State Options
NSArray *timeTitles = @[@"Upcoming", @"History"];
for (int i = 0; i < timeTitles.count; i++) {
UIButton *b = [UIButton buttonWithType:UIButtonTypeCustom];
b.frame = CGRectMake(0, timeStartY + i * 50, self.view.bounds.size.width, 50);
[b setTitle:timeTitles[i] forState:UIControlStateNormal];
[b addTarget:self action:@selector(onFilterTimeTap:) forControlEvents:UIControlEventTouchUpInside];
b.tag = 200 + i;
[self.filterOverlay addSubview:b];
UIImageView *radio = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.bounds.size.width - 40,
timeStartY + i * 50 + 12,
24, 24)];
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, y, contentView.bounds.size.width, 50)];
btn.tag = 200 + i;
[btn addTarget:self action:@selector(onFilterTimeTap:) forControlEvents:UIControlEventTouchUpInside];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, btn.bounds.size.width - 60, 50)];
label.text = timeTitles[i];
label.font = [UIFont systemFontOfSize:16];
label.textColor = [UIColor blackColor];
[btn addSubview:label];
UIImageView *radio = [[UIImageView alloc] initWithFrame:CGRectMake(btn.bounds.size.width - 40, 13, 24, 24)];
radio.tag = 300 + i;
radio.image = [self.apptFilterTime isEqualToString:timeTitles[i]]
? [UIImage imageNamed:@"radio_check"]
: [UIImage imageNamed:@"radio_uncheck"];
[self.filterOverlay addSubview:radio];
}
radio.image = [self.apptFilterTime isEqualToString:timeTitles[i]] ? [UIImage imageNamed:@"radio_check"] : [UIImage imageNamed:@"radio_uncheck"];
[btn addSubview:radio];
// Add done/back handling (tap background to close)
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideFilterOverlay)];
[self.filterOverlay addGestureRecognizer:tap];
[contentView addSubview:btn];
y += 50;
}
// finally add to main view
[self.view addSubview:self.filterOverlay];
}
......@@ -580,6 +804,7 @@
NSString *selected = typeTitles[sender.tag];
self.apptFilterType = selected;
self.filterOverlay.hidden = YES;
self.filterOverlay.userInteractionEnabled = NO;
self.blnLoading = YES;
[self.loadingView startAnimating];
// process
......@@ -596,6 +821,7 @@
self.apptFilterTime = selected;
self.filterOverlay.hidden = YES;
self.filterOverlay.userInteractionEnabled = NO;
self.blnLoading = YES;
[self.loadingView startAnimating];
......@@ -608,6 +834,12 @@
- (void)hideFilterOverlay {
self.filterOverlay.hidden = YES;
self.filterOverlay.userInteractionEnabled = NO;
[self.view bringSubviewToFront:self.topBar];
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}
- (void)refreshFilterOverlay {
......@@ -628,6 +860,21 @@
}
}
- (UILabel *)createFilterHeaderWithText:(NSString *)text y:(CGFloat)y parent:(UIView *)parent {
UILabel *header = [[UILabel alloc] initWithFrame:CGRectMake(16, y, parent.bounds.size.width - 32, 24)];
header.text = text;
header.font = [UIFont boldSystemFontOfSize:16];
header.textColor = [UIColor darkGrayColor];
[parent addSubview:header];
// underline
UIView *underline = [[UIView alloc] initWithFrame:CGRectMake(16, y + 26, parent.bounds.size.width - 32, 1)];
underline.backgroundColor = [UIColor lightGrayColor];
[parent addSubview:underline];
return header;
}
#pragma mark - Loading Overlay
- (void)setupLoadingOverlay {
self.loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
......@@ -664,6 +911,7 @@
- (void)onFilterButton {
self.filterOverlay.hidden = !self.filterOverlay.hidden;
self.filterOverlay.userInteractionEnabled = !self.filterOverlay.userInteractionEnabled;
}
- (void)onMakeAppointment {
......@@ -917,6 +1165,7 @@
}
}
[self.agendaTable reloadData];
[self prepareAgendaSections];
}
- (UIColor *)colorFromHex:(NSString *)hex {
......
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