Commit 106bf6c9 authored by Wei Han's avatar Wei Han

readme update

parent b3b7d2a9
......@@ -4,6 +4,10 @@
<dict>
<key>files</key>
<dict>
<key>Assets.car</key>
<data>
KnMHH8GU4NZESWbs6/0uU5WX8uA=
</data>
<key>Headers/QmsPlugin.h</key>
<data>
k1ElB+OnAYDlAZsTB5il2X25Zbo=
......@@ -23,6 +27,13 @@
</dict>
<key>files2</key>
<dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
aLAhYopvL1Lb4CTMG5A8x8RAmnkpXwHQbjsJdpq8gYg=
</data>
</dict>
<key>Headers/QmsPlugin.h</key>
<dict>
<key>hash2</key>
......
......@@ -4,6 +4,10 @@
<dict>
<key>files</key>
<dict>
<key>Assets.car</key>
<data>
KnMHH8GU4NZESWbs6/0uU5WX8uA=
</data>
<key>Headers/QmsPlugin.h</key>
<data>
k1ElB+OnAYDlAZsTB5il2X25Zbo=
......@@ -23,6 +27,13 @@
</dict>
<key>files2</key>
<dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
aLAhYopvL1Lb4CTMG5A8x8RAmnkpXwHQbjsJdpq8gYg=
</data>
</dict>
<key>Headers/QmsPlugin.h</key>
<dict>
<key>hash2</key>
......
......@@ -4,7 +4,7 @@
@interface DashboardAPIClient : NSObject
+ (void)fetchDashboardSummary:(void (^)(NSDictionary *data))completion;
+ (void)fetchAnnouncements:(void (^)(NSArray *data))completion;
+ (void)fetchAnnouncement:(void (^)(NSArray *data))completion;
+ (void)fetchAppointments:(void (^)(NSArray *data))completion;
+ (void)fetchIssues:(void (^)(NSArray *data))completion;
+ (void)fetchHeaderInfo:(void (^)(NSDictionary *data))completion;
......
......@@ -15,18 +15,6 @@
}] resume];
}
+ (void)fetchAnnouncements:(void (^)(NSArray *))completion {
NSURL *url = [NSURL URLWithString:@"https://jsonplaceholder.typicode.com/posts?_limit=5"];
[[[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) { completion(nil); return; }
NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
completion(json);
});
}] resume];
}
+ (void)fetchAppointments:(void (^)(NSArray *))completion {
NSURL *url = [NSURL URLWithString:@"https://jsonplaceholder.typicode.com/todos?_limit=5"];
[[[NSURLSession sharedSession] dataTaskWithURL:url
......@@ -68,42 +56,48 @@
}
+ (void)fetchDashboardInfo:(void (^)(NSDictionary *data, NSError *error))completion {
NSString *baseURL = @"https://kitadev.commudesk.com/api/owner/plan/getIssueUpdateAppointmentInfo";
NSString *token = @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
// 👇 Append token as query parameter
NSString *fullURL = [NSString stringWithFormat:@"%@?token=%@", baseURL, token];
NSURL *url = [NSURL URLWithString:fullURL];
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/plan/getIssueUpdateAppointmentInfo";
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSDictionary *body = @{
@"os": @"AND",
@"drawing_plan_id": @135,
@"information": @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
}
// 🪪 Authorization header
NSString *token = @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
[request setValue:[NSString stringWithFormat:@"Bearer %@", token] forHTTPHeaderField:@"Authorization"];
// 🧱 Create multipart form-data body
NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
NSMutableData *body = [NSMutableData data];
// Helper block to append fields
void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"%@\r\n", value] dataUsingEncoding:NSUTF8StringEncoding]];
};
NSError *jsonError = nil;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:body options:0 error:&jsonError];
if (jsonError) {
if (completion) completion(nil, jsonError);
return;
}
request.HTTPBody = bodyData;
// Add form fields
appendFormField(@"data[os]", @"AND");
appendFormField(@"data[drawing_plan_id]", @"135");
NSString *infoJson = @"{\"OS\":\"AND\",\"IMEI\":\"AND:2a2f13ff17b1cbb5\",\"OS_VERSION\":\"35\",\"MODEL\":\"2306EPN60G\",\"APP_VERSION\":\"1.22.26\"}";
appendFormField(@"data[information]", infoJson);
NSLog(@"🌍 Final URL: %@", url.absoluteString);
NSLog(@"📦 Body: %@", [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding]);
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request
request.HTTPBody = body;
// 🌍 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🪪 Authorization: Bearer %@", token);
NSLog(@"📦 Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Network error: %@", error);
......@@ -121,6 +115,64 @@
});
}
}];
[task resume];
}
+ (void)fetchAnnouncement:(void (^)(NSArray *data))completion {
NSURL *url = [NSURL URLWithString:
@"https://kitadev.commudesk.com/api/announcement?token=YOUR_TOKEN_HERE"];
NSDictionary *body = @{
@"data": @{
@"os": @"iOS",
@"information": @{
@"OS": @"iOS",
@"IMEI": @"iOS:1234567890",
@"OS_VERSION": [[UIDevice currentDevice] systemVersion],
@"MODEL": [[UIDevice currentDevice] model],
@"APP_VERSION": [[[NSBundle mainBundle] infoDictionary]
objectForKey:@"CFBundleShortVersionString"]
}
}
};
NSError *jsonError;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:body options:0 error:&jsonError];
if (jsonError) {
NSLog(@"❌ JSON serialization failed: %@", jsonError);
if (completion) completion(@[]);
return;
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
request.HTTPBody = jsonData;
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Request failed: %@", error.localizedDescription);
if (completion) completion(@[]);
return;
}
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"✅ Announcement API response:\n%@", json);
NSDictionary *appData = json[@"AppData"];
if ([appData[@"status"] isEqualToString:@"success"]) {
NSArray *announcements = json[@"Data"];
NSLog(@"📣 Got %lu announcements", (unsigned long)announcements.count);
if (completion) completion(announcements);
} else {
NSLog(@"⚠️ Announcement fetch failed: %@", appData[@"message"]);
if (completion) completion(@[]);
}
}];
[task resume];
}
......
......@@ -5,6 +5,27 @@
#import "PlanViewController.h"
#import "DashboardAPIClient.h"
@interface DashboardViewController ()
@property (nonatomic, strong) NSArray *appointments;
@property (nonatomic, strong) NSArray *issues;
@property (nonatomic, assign) BOOL hasPendingAccess;
@property (nonatomic, assign) BOOL hasSignOff;
@property (nonatomic, strong) NSString *strContactlessHandoverStatus;
@property (nonatomic, strong) NSString *strContactlessFormUrl;
@property (nonatomic, strong) NSString *strPendingAccessReminder;
@property (nonatomic, assign) BOOL bln_Block_Handover;
@property (nonatomic, assign) BOOL bln_Block_Inspection;
@property (nonatomic, assign) NSInteger intPOACount;
@property (nonatomic, assign) NSInteger intPendingAccessCount;
@property (nonatomic, assign) BOOL bln_ReminderPA;
@property (nonatomic, assign) BOOL bln_POASignOff;
@property (nonatomic, strong) NSString *projectCode;
@property (nonatomic, strong) NSString *projectName;
@property (nonatomic, strong) UIImage *headerBackgroundImage;
@end
@implementation DashboardViewController {
DashboardHeaderView *_headerView;
UIScrollView *_scrollView;
......@@ -22,17 +43,41 @@
[self setupScrollContent];
[self setupFooter];
// 👇 TEMPORARY: test real API
self.projectCode = @"PTD197656";
self.projectName = @"Ara E`Residence Dev";
[DashboardAPIClient fetchDashboardInfo:^(NSDictionary *data, NSError *error) {
if (error) {
NSLog(@"❌ fetchDashboardInfo error: %@", error.localizedDescription);
return;
}
NSLog(@"🧩 Dashboard Info response: %@", data);
NSLog(@"🧩 Dashboard Info response received");
[self processDashboardResponse:data];
// 💾 Optionally, save full JSON to a file for reference
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"DashboardInfoResponse.txt"];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:nil];
[jsonData writeToFile:filePath atomically:YES];
NSLog(@"📁 Dashboard API response saved to %@", filePath);
}];
// existing mock data calls
[self fetchDashboardData];
[DashboardAPIClient fetchAnnouncement:^(NSDictionary *data, NSError *error) {
if (error) {
NSLog(@"❌ fetchAnnouncement error: %@", error.localizedDescription);
return;
}
NSLog(@"🗞️ Announcement response: %@", data);
// Optional: save to file for reference
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"AnnouncementResponse.txt"];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:nil];
[jsonData writeToFile:filePath atomically:YES];
NSLog(@"📁 Announcement response saved to %@", filePath);
}];
}
#pragma mark - Layout
......@@ -56,10 +101,26 @@
- (void)setupHeader {
CGFloat headerHeight = 220.0;
_headerView = [[DashboardHeaderView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, headerHeight)];
// Remote background image URL
NSString *remoteHeaderURL = @"https://kitadev.commudesk.com/uploads/marketing_photo/164543837127.png";
// Load image asynchronously so it doesn’t block UI
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:remoteHeaderURL];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *remoteImage = nil;
if (data) {
remoteImage = [UIImage imageWithData:data];
}
self.headerBackgroundImage = remoteImage ?: [UIImage imageNamed:@"header_bg"];
dispatch_async(dispatch_get_main_queue(), ^{
[_headerView configureWithProjectName:@"Project A"
unitName:@"Unit 3A"
backgroundImage:[UIImage imageNamed:@"header_bg"]
backgroundImage:self.headerBackgroundImage
overlayImage:[UIImage imageNamed:@"overlay"]];
});
});
[self.view addSubview:_headerView];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeSystem];
......@@ -274,124 +335,570 @@
}];
}
#pragma mark - Networking
- (void)fetchDashboardData {
__weak typeof(self) weakSelf = self;
[DashboardAPIClient fetchDashboardSummary:^(NSDictionary *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"🏗 Dashboard Summary: %@", data);
// In the future: update header info (project/unit name, etc.)
[strongSelf->_headerView configureWithProjectName:data[@"name"]
unitName:data[@"username"]
backgroundImage:[UIImage imageNamed:@"header_bg"]
overlayImage:[UIImage imageNamed:@"overlay"]];
}];
#pragma mark - Dashboard Data Processor
[DashboardAPIClient fetchHeaderInfo:^(NSDictionary *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
- (void)processDashboardResponse:(NSDictionary *)data {
NSDictionary *appData = data[@"AppData"];
NSDictionary *mainData = data[@"Data"];
NSLog(@"🖼 Header info: %@", data);
if (![appData[@"status"] isEqualToString:@"success"] || !mainData) {
NSLog(@"⚠️ Dashboard status not success: %@", appData[@"message"]);
return;
}
NSString *projectName = data[@"title"] ?: @"Project A";
NSString *unitName = @"Demo Unit 3A";
NSString *imageURL = data[@"url"];
NSArray *appointments = mainData[@"appointment"] ?: @[];
NSArray *issues = mainData[@"issue_update"] ?: @[];
NSNumber *paCount = mainData[@"pa_count"];
NSNumber *poaCount = mainData[@"poa_count"];
NSString *accessMessage = mainData[@"access_item_message"];
NSString *accessTNC = mainData[@"access_item_terms_and_condition"];
NSString *handoverStatus = mainData[@"contactlessHandoverTempStatus"];
NSString *handoverURL = mainData[@"contactlessHandoverTempURL"];
BOOL hasPendingAccess = (paCount && paCount.intValue > 0);
BOOL hasSignOff = (poaCount && poaCount.intValue > 0);
BOOL blockHandover = NO;
BOOL blockInspection = NO;
NSInteger rectificationIssueCount = 0;
for (NSDictionary *appt in appointments) {
NSString *type = [appt[@"appointment_type"] lowercaseString];
NSString *status = [appt[@"status"] lowercaseString];
if ([type isEqualToString:@"handover"] && [status isEqualToString:@"active"]) {
blockHandover = YES;
} else if ([type isEqualToString:@"joint inspection"] && [status isEqualToString:@"active"]) {
blockInspection = YES;
} else if ([type isEqualToString:@"rectification"] && ![status isEqualToString:@"cancelled"]) {
NSArray *issuesList = appt[@"issues"];
if ([issuesList isKindOfClass:[NSArray class]]) {
rectificationIssueCount += issuesList.count;
}
}
}
NSURL *url = [NSURL URLWithString:imageURL];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Failed to download image: %@", error.localizedDescription);
return;
// If rectification count == pending access count, clear reminder
if (paCount.intValue == rectificationIssueCount) {
hasPendingAccess = NO;
}
UIImage *image = [UIImage imageWithData:data];
if (!image) return;
// --- Store results ---
self.appointments = appointments;
self.issues = issues;
self.hasPendingAccess = hasPendingAccess;
self.hasSignOff = hasSignOff;
self.intPOACount = poaCount.intValue;
self.intPendingAccessCount = paCount.intValue;
self.bln_Block_Handover = blockHandover;
self.bln_Block_Inspection = blockInspection;
self.strPendingAccessReminder = accessMessage ?: @"";
self.strContactlessHandoverStatus = handoverStatus ?: @"";
self.strContactlessFormUrl = handoverURL ?: @"";
NSLog(@"✅ Dashboard parsed:");
NSLog(@" • Appointments: %lu", (unsigned long)appointments.count);
NSLog(@" • Issues: %lu", (unsigned long)issues.count);
NSLog(@" • Pending Access: %d", hasPendingAccess);
NSLog(@" • POA Count: %ld", (long)self.intPOACount);
NSLog(@" • Block Handover: %d", blockHandover);
NSLog(@" • Block Inspection: %d", blockInspection);
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf->_headerView configureWithProjectName:projectName
unitName:unitName
backgroundImage:image
[_headerView configureWithProjectName:self.projectName
unitName:self.projectCode
backgroundImage:self.headerBackgroundImage
overlayImage:[UIImage imageNamed:@"overlay"]];
[self refreshDashboardSectionsWithData:mainData];
});
}];
[task resume];
NSLog(@"🖼 Header image URL: %@", imageURL);
}];
}
[DashboardAPIClient fetchAnnouncements:^(NSArray *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"📢 Announcements: %@", data);
[strongSelf populateSection:@"Project Update" withData:data];
}];
- (void)refreshDashboardSectionsWithData:(NSDictionary *)mainData {
NSLog(@"🔄 Refreshing dashboard sections...");
[DashboardAPIClient fetchAppointments:^(NSArray *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"📅 Appointments: %@", data);
[strongSelf populateSection:@"Appointments" withData:data];
}];
// Remove any old views from scroll
for (UIView *view in _contentContainer.subviews) {
[view removeFromSuperview];
}
_currentY = 16.0;
[DashboardAPIClient fetchIssues:^(NSArray *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"🪲 Issues: %@", data);
[strongSelf populateSection:@"Issue Updates" withData:data];
}];
}
// === PROJECT UPDATES ===
NSArray *announcements = mainData[@"announcement"];
if (announcements.count > 0) {
UIView *announcementSection = [self buildAnnouncementSection:announcements];
CGRect f = announcementSection.frame;
f.origin.y = _currentY;
announcementSection.frame = f;
[_contentContainer addSubview:announcementSection];
_currentY += CGRectGetHeight(announcementSection.frame) + 12;
}
- (UIImage *)downloadImageFrom:(NSString *)urlString {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
return data ? [UIImage imageWithData:data] : [UIImage imageNamed:@"header_bg"];
// === APPOINTMENTS ===
NSArray *appointments = mainData[@"appointment"];
if (appointments.count > 0) {
UIView *appointmentSection = [self buildAppointmentSection:appointments];
CGRect f = appointmentSection.frame;
f.origin.y = _currentY;
appointmentSection.frame = f;
[_contentContainer addSubview:appointmentSection];
_currentY += CGRectGetHeight(appointmentSection.frame) + 12;
}
// === ISSUE UPDATES ===
NSArray *issues = mainData[@"issue_update"];
if (issues.count > 0) {
UIView *issueSection = [self buildIssueSection:issues];
CGRect f = issueSection.frame;
f.origin.y = _currentY;
issueSection.frame = f;
[_contentContainer addSubview:issueSection];
_currentY += CGRectGetHeight(issueSection.frame) + 16;
}
_contentContainer.frame = CGRectMake(0, 0, _scrollView.bounds.size.width, _currentY);
_scrollView.contentSize = CGSizeMake(_scrollView.bounds.size.width, _currentY);
NSLog(@"✅ Dashboard UI refreshed with %lu announcements, %lu appointments, %lu issues",
(unsigned long)announcements.count, (unsigned long)appointments.count, (unsigned long)issues.count);
}
- (void)populateSection:(NSString *)title withData:(NSArray *)data {
CGFloat y = _currentY;
- (UIView *)buildAnnouncementSection:(NSArray *)announcements {
UIView *section = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 160)];
// --- Section Header ---
UILabel *header = [[UILabel alloc] initWithFrame:CGRectMake(16, y, self.view.bounds.size.width - 32, 24)];
header.text = title;
header.font = [UIFont boldSystemFontOfSize:18];
[_contentContainer addSubview:header];
// --- Title row ---
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, 200, 24)];
title.text = @"Project Update";
title.font = [UIFont boldSystemFontOfSize:18];
title.userInteractionEnabled = YES; // 👈 enable taps
y += 34;
UITapGestureRecognizer *titleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
[title addGestureRecognizer:titleTap];
[section addSubview:title];
// --- Special handling for Project Update ---
if ([title isEqualToString:@"Project Update"]) {
UIScrollView *hScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, y, self.view.bounds.size.width, 140)];
hScroll.showsHorizontalScrollIndicator = NO;
hScroll.autoresizingMask = UIViewAutoresizingFlexibleWidth;
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_green_arrowright"]];
arrow.frame = CGRectMake(self.view.bounds.size.width - 40, 2, 24, 24);
[section addSubview:arrow];
// --- Horizontal scroll for cards ---
UIScrollView *hScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 40, self.view.bounds.size.width, 110)];
hScroll.showsHorizontalScrollIndicator = NO;
CGFloat x = 16;
for (NSDictionary *item in data) {
UIView *card = [self createCardWithText:item[@"title"] ?: @"No title"
frame:CGRectMake(x, 0, 220, 120)];
for (NSDictionary *item in announcements) {
UIView *card = [self makeAnnouncementCard:item];
CGRect frame = card.frame;
frame.origin.x = x;
frame.origin.y = 0;
card.frame = frame;
[hScroll addSubview:card];
x += 236; // card width + spacing
x += frame.size.width + 12;
}
hScroll.contentSize = CGSizeMake(x, 110);
[section addSubview:hScroll];
return section;
}
- (UIView *)buildAppointmentSection:(NSArray *)appointments {
UIView *section = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 0)];
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, 200, 24)];
title.text = @"Appointments";
title.font = [UIFont boldSystemFontOfSize:18];
[section addSubview:title];
title.userInteractionEnabled = YES; // 👈 enable taps
UITapGestureRecognizer *titleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
[title addGestureRecognizer:titleTap];
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_green_arrowright"]];
arrow.frame = CGRectMake(self.view.bounds.size.width - 40, 2, 24, 24);
[section addSubview:arrow];
CGFloat y = 34;
CGFloat cardWidth = self.view.bounds.size.width * 0.9; // 90% of screen width
CGFloat xCenter = (self.view.bounds.size.width - cardWidth) / 2.0;
NSArray *limitedAppointments = (appointments.count > 3)
? [appointments subarrayWithRange:NSMakeRange(0, 3)]
: appointments;
for (NSDictionary *item in limitedAppointments) {
UIView *card = [self makeAppointmentCard:item width:cardWidth];
CGRect frame = card.frame;
frame.origin.x = xCenter;
frame.origin.y = y;
card.frame = frame;
[section addSubview:card];
y += frame.size.height + 12; // spacing between cards
}
section.frame = CGRectMake(0, 0, self.view.bounds.size.width, y + 10);
return section;
}
- (UIView *)buildIssueSection:(NSArray *)issues {
UIView *section = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 0)];
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, 200, 24)];
title.text = @"Issue Updates";
title.font = [UIFont boldSystemFontOfSize:18];
[section addSubview:title];
title.userInteractionEnabled = YES; // 👈 enable taps
UITapGestureRecognizer *titleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
[title addGestureRecognizer:titleTap];
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_green_arrowright"]];
arrow.frame = CGRectMake(self.view.bounds.size.width - 40, 2, 24, 24);
[section addSubview:arrow];
CGFloat y = 34;
CGFloat cardWidth = self.view.bounds.size.width * 0.9; // 90% of screen width
CGFloat xCenter = (self.view.bounds.size.width - cardWidth) / 2.0;
for (NSDictionary *item in issues) {
UIView *card = [self makeIssueCard:item width:cardWidth];
CGRect frame = card.frame;
frame.origin.x = xCenter;
frame.origin.y = y;
card.frame = frame;
[section addSubview:card];
y += frame.size.height + 12;
}
section.frame = CGRectMake(0, 0, self.view.bounds.size.width, y + 10);
return section;
}
- (UILabel *)makeSectionTitle:(NSString *)titleText {
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, 200, 24)];
title.text = titleText;
title.font = [UIFont boldSystemFontOfSize:18];
title.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
[title addGestureRecognizer:tap];
return title;
}
- (UIView *)makeAnnouncementCard:(NSDictionary *)item {
UIView *card = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 280, 100)];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 8;
card.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor;
card.layer.shadowOpacity = 0.3;
card.layer.shadowOffset = CGSizeMake(0, 1);
UILabel *date = [[UILabel alloc] initWithFrame:CGRectMake(12, 8, 200, 16)];
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDate *dateObj = [fmt dateFromString:item[@"created_at"]];
fmt.dateFormat = @"dd/MM/yyyy";
date.text = dateObj ? [fmt stringFromDate:dateObj] : @"--/--/----";
date.textColor = [UIColor grayColor];
date.font = [UIFont systemFontOfSize:13];
[card addSubview:date];
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(12, 28, 256, 60)];
title.text = item[@"title"] ?: @"Untitled announcement";
title.numberOfLines = 3;
title.font = [UIFont boldSystemFontOfSize:16];
[card addSubview:title];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleAnnouncementTap:)];
[card addGestureRecognizer:tap];
return card;
}
- (UIView *)makeAppointmentCard:(NSDictionary *)item width:(CGFloat)width {
UIView *card = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 90)];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 8;
card.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor;
card.layer.shadowOpacity = 0.3;
card.layer.shadowOffset = CGSizeMake(0, 1);
// Extract fields
NSString *appointmentType = item[@"appointment_type"] ?: @"-";
NSString *status = item[@"status"] ?: @"-";
NSString *startDate = item[@"start_date"] ?: @"-";
NSString *startTime = item[@"start_time"] ?: @"-";
NSString *buyerName = @"-";
NSArray *buyers = item[@"primary_buyer"];
if ([buyers isKindOfClass:[NSArray class]] && buyers.count > 0) {
NSDictionary *buyer = buyers.firstObject;
if ([buyer[@"name"] isKindOfClass:[NSString class]]) {
buyerName = buyer[@"name"];
}
}
// Notification Icon
UIView *iconCircle = [[UIView alloc] initWithFrame:CGRectMake(12, 14, 24, 24)];
iconCircle.backgroundColor = [UIColor colorWithRed:1.0 green:0.4 blue:0.4 alpha:1.0]; // red tone
iconCircle.layer.cornerRadius = 12;
UIImageView *bell = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"bell.fill"]];
bell.tintColor = UIColor.whiteColor;
bell.frame = CGRectMake(4, 4, 16, 16);
[iconCircle addSubview:bell];
[card addSubview:iconCircle];
// === Status ===
UILabel *statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(44, 12, 180, 26)];
statusLabel.text = status;
statusLabel.font = [UIFont boldSystemFontOfSize:17];
statusLabel.textColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // teal
[card addSubview:statusLabel];
// === Time (right aligned) ===
UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(width - 90, 12, 80, 26)];
timeLabel.textAlignment = NSTextAlignmentRight;
timeLabel.text = startTime;
timeLabel.font = [UIFont boldSystemFontOfSize:15];
[card addSubview:timeLabel];
// === Description text ===
NSString *desc = [NSString stringWithFormat:
@"You have a %@ appointment on %@, %@",
appointmentType, startDate, buyerName];
UILabel *descLabel = [[UILabel alloc] initWithFrame:CGRectMake(16, 46, width - 32, 36)];
descLabel.text = desc;
descLabel.font = [UIFont systemFontOfSize:15];
descLabel.numberOfLines = 2;
descLabel.textColor = [UIColor blackColor];
[card addSubview:descLabel];
// === Tap animation ===
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleAppointmentTap:)];
[card addGestureRecognizer:tap];
return card;
}
- (UIView *)makeIssueCard:(NSDictionary *)item width:(CGFloat)width {
UIView *card = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 110)];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 8;
card.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor;
card.layer.shadowOpacity = 0.3;
card.layer.shadowOffset = CGSizeMake(0, 1);
card.layer.masksToBounds = NO;
// === Extract fields ===
NSString *status = item[@"status_external"] ?: @"";
NSString *issueRef = item[@"issue_reference"] ?: @"";
NSString *createdAt = item[@"created_at"] ?: @"";
NSString *formattedDate = [self formatDate:createdAt];
UIColor *statusColor = [self colorForStatus:status];
// === Determine issue image (like renderOnlineImage) ===
NSString *issueImage = item[@"image"];
if (!issueImage || [issueImage isEqualToString:@""]) {
NSArray *firstArray = item[@"first"];
NSArray *lastArray = item[@"last"];
if (firstArray.count > 0 && firstArray[0][@"image"])
issueImage = firstArray[0][@"image"];
else if (lastArray.count > 0 && lastArray[0][@"image"])
issueImage = lastArray[0][@"image"];
}
// === Top row: Status + Date ===
CGFloat iconSize = 16;
UIView *statusDot = [[UIView alloc] initWithFrame:CGRectMake(12, 12, iconSize, iconSize)];
statusDot.layer.cornerRadius = iconSize / 2;
statusDot.backgroundColor = statusColor;
[card addSubview:statusDot];
UILabel *statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(statusDot.frame) + 6, 8, width - 160, 22)];
statusLabel.text = status.length ? status : @"Unknown";
statusLabel.textColor = statusColor;
statusLabel.font = [UIFont boldSystemFontOfSize:15];
[card addSubview:statusLabel];
UILabel *dateLabel = [[UILabel alloc] initWithFrame:CGRectMake(width - 130, 10, 118, 18)];
dateLabel.textAlignment = NSTextAlignmentRight;
dateLabel.text = formattedDate;
dateLabel.textColor = [UIColor grayColor];
dateLabel.font = [UIFont systemFontOfSize:13];
[card addSubview:dateLabel];
// === Divider line ===
// UIView *divider = [[UIView alloc] initWithFrame:CGRectMake(12, 36, width - 24, 0.5)];
// divider.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
// [card addSubview:divider];
// === Image + Info Row ===
CGFloat imageSize = 44;
UIImageView *thumbView = [[UIImageView alloc] initWithFrame:CGRectMake(12, 48, imageSize, imageSize)];
thumbView.layer.cornerRadius = 6;
thumbView.clipsToBounds = YES;
thumbView.contentMode = UIViewContentModeScaleAspectFill;
if (issueImage && issueImage.length > 0) {
NSURL *url = [NSURL URLWithString:issueImage];
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
thumbView.image = [UIImage imageWithData:data];
} else {
thumbView.image = [UIImage imageNamed:@"small_placeholder"];
}
} else {
thumbView.image = [UIImage imageNamed:@"small_placeholder"];
}
hScroll.contentSize = CGSizeMake(x, 140);
[_contentContainer addSubview:hScroll];
[card addSubview:thumbView];
y += 150; // spacing after horizontal list
// === Text beside image ===
CGFloat textX = CGRectGetMaxX(thumbView.frame) + 10;
UILabel *infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(textX, 46, width - textX - 12, 50)];
infoLabel.numberOfLines = 3;
infoLabel.textColor = [UIColor blackColor];
infoLabel.font = [UIFont systemFontOfSize:15];
infoLabel.attributedText = [self notificationTextForIssue:item];
[card addSubview:infoLabel];
// === Tap gesture ===
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIssueTap:)];
[card addGestureRecognizer:tap];
return card;
}
// Convert hex string (like "#F14915") to UIColor
- (UIColor *)colorFromHex:(NSString *)hexString {
unsigned rgbValue = 0;
NSScanner *scanner = [NSScanner scannerWithString:hexString];
[scanner setScanLocation:1];
[scanner scanHexInt:&rgbValue];
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0
green:((rgbValue & 0x00FF00) >> 8) / 255.0
blue:(rgbValue & 0x0000FF) / 255.0
alpha:1.0];
}
// Status → Color
- (UIColor *)colorForStatus:(NSString *)status {
if ([status isEqualToString:@"Lodged"]) return [self colorFromHex:@"#2952D6"];
if ([status isEqualToString:@"New"]) return [self colorFromHex:@"#F99100"];
if ([status isEqualToString:@"Pending Start"]) return [self colorFromHex:@"#BD5607"];
if ([status isEqualToString:@"Withdraw"] || [status isEqualToString:@"WITHDRAW"] || [status isEqualToString:@"VOID"])
return [self colorFromHex:@"#EFC089"];
if ([status isEqualToString:@"WIP"] || [status isEqualToString:@"W.I.P"])
return [self colorFromHex:@"#F6C607"];
if ([status isEqualToString:@"Completed"]) return [self colorFromHex:@"#DF00D0"];
if ([status isEqualToString:@"Pending Access"]) return [self colorFromHex:@"#F14915"];
if ([status isEqualToString:@"Pending Owner Acceptance"]) return [self colorFromHex:@"#EF7215"];
if ([status isEqualToString:@"Rejected"]) return [self colorFromHex:@"#FF3939"];
if ([status isEqualToString:@"Close Internal"]) return [self colorFromHex:@"#58BA63"];
if ([status isEqualToString:@"Close External"]) return [self colorFromHex:@"#1C7C00"];
return [UIColor darkGrayColor];
}
- (NSAttributedString *)notificationTextForIssue:(NSDictionary *)issue {
NSString *status = issue[@"status_external"] ?: @"";
NSString *issueRef = issue[@"issue_reference"] ?: @"";
NSString *projectName = self.projectName; // Replace with GLOBALS.PROJECT_NAME if available
// Base sentence builder
NSString *text = @"";
if ([status isEqualToString:@"New"]) {
text = [NSString stringWithFormat:@"New issue %@ from %@ has been created.", issueRef, projectName];
} else if ([status isEqualToString:@"Lodged"]) {
text = [NSString stringWithFormat:@"%@ from %@ has been lodged.", issueRef, projectName];
} else if ([status isEqualToString:@"Pending Start"]) {
text = [NSString stringWithFormat:@"%@ from %@ is pending start.", issueRef, projectName];
} else if ([status isEqualToString:@"Pending Access"]) {
text = [NSString stringWithFormat:@"%@ from %@ is pending access.", issueRef, projectName];
} else if ([status isEqualToString:@"W.I.P"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to W.I.P.", issueRef, projectName];
} else if ([status isEqualToString:@"Not Me"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to Not Me.", issueRef, projectName];
} else if ([status isEqualToString:@"Reassign"]) {
text = [NSString stringWithFormat:@"%@ from %@ needs to be reassigned.", issueRef, projectName];
} else if ([status isEqualToString:@"Completed"]) {
text = [NSString stringWithFormat:@"%@ from %@ is completed.", issueRef, projectName];
} else if ([status isEqualToString:@"Redo"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to Redo.", issueRef, projectName];
} else if ([status isEqualToString:@"Close Internal"]) {
text = [NSString stringWithFormat:@"%@ from %@ is closed internally.", issueRef, projectName];
} else if ([status isEqualToString:@"Close External"]) {
text = [NSString stringWithFormat:@"%@ from %@ is closed externally.", issueRef, projectName];
} else if ([status isEqualToString:@"Rejected"] ||
[status isEqualToString:@"Decline"] ||
[status isEqualToString:@"Withdraw"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to %@.", issueRef, projectName, status];
} else if ([status isEqualToString:@"Pending Owner Acceptance"] ||
[status isEqualToString:@"POA"] ||
[status isEqualToString:@"poa"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to Pending Owner Acceptance.", issueRef, projectName];
} else {
// --- Vertical cards for all other sections ---
for (NSDictionary *item in data) {
UIView *card = [self createCardWithText:item[@"title"] ?: @"No title"
frame:CGRectMake(16, y, self.view.bounds.size.width - 32, 100)];
[_contentContainer addSubview:card];
y += 120;
text = [NSString stringWithFormat:@"Issue %@ (%@)", issueRef, status];
}
y += 20; // extra bottom padding between sections
// === Create the base attributed string ===
NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] initWithString:text
attributes:@{
NSFontAttributeName: [UIFont systemFontOfSize:15],
NSForegroundColorAttributeName: UIColor.blackColor
}];
// === Highlight the status substring ===
UIColor *statusColor = [self colorForStatus:status];
NSRange statusRange = [text rangeOfString:status options:NSCaseInsensitiveSearch];
if (statusRange.location != NSNotFound) {
[attrText addAttributes:@{
NSFontAttributeName: [UIFont boldSystemFontOfSize:15],
NSForegroundColorAttributeName: statusColor
} range:statusRange];
}
_currentY = y;
_contentContainer.frame = CGRectMake(0, 0, _scrollView.bounds.size.width, _currentY);
_scrollView.contentSize = CGSizeMake(_scrollView.bounds.size.width, _currentY);
return attrText;
}
- (NSString *)formatDate:(NSString *)inputDateString {
if (!inputDateString || inputDateString.length == 0) return @"--/--/----";
NSDateFormatter *inputFmt = [[NSDateFormatter alloc] init];
inputFmt.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDate *date = [inputFmt dateFromString:inputDateString];
if (!date) return inputDateString;
NSDateFormatter *outputFmt = [[NSDateFormatter alloc] init];
outputFmt.dateFormat = @"dd/MM/yyyy";
return [outputFmt stringFromDate:date];
}
#pragma mark - Individual Card Taps
- (void)handleAnnouncementTap:(UITapGestureRecognizer *)gesture {
UIView *card = gesture.view;
[self animateTapForView:card];
NSLog(@"📰 Announcement card tapped");
// TODO: open AnnouncementDetailsScreen equivalent in future
}
- (void)handleAppointmentTap:(UITapGestureRecognizer *)gesture {
UIView *card = gesture.view;
[self animateTapForView:card];
NSLog(@"📅 Appointment card tapped");
// TODO: open AppointmentDetails in future
}
- (void)handleIssueTap:(UITapGestureRecognizer *)gesture {
UIView *card = gesture.view;
[self animateTapForView:card];
NSLog(@"🪲 Issue card tapped");
// TODO: open IssueDetails later
}
- (UIImage *)downloadImageFrom:(NSString *)urlString {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
return data ? [UIImage imageWithData:data] : [UIImage imageNamed:@"header_bg"];
}
#pragma mark - Card Factory with Tap Animation
......
{
"images" : [
{
"filename" : "overlay.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -4,6 +4,10 @@
<dict>
<key>files</key>
<dict>
<key>Assets.car</key>
<data>
KnMHH8GU4NZESWbs6/0uU5WX8uA=
</data>
<key>Headers/QmsPlugin.h</key>
<data>
k1ElB+OnAYDlAZsTB5il2X25Zbo=
......@@ -23,6 +27,13 @@
</dict>
<key>files2</key>
<dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
aLAhYopvL1Lb4CTMG5A8x8RAmnkpXwHQbjsJdpq8gYg=
</data>
</dict>
<key>Headers/QmsPlugin.h</key>
<dict>
<key>hash2</key>
......
......@@ -4,6 +4,10 @@
<dict>
<key>files</key>
<dict>
<key>Assets.car</key>
<data>
KnMHH8GU4NZESWbs6/0uU5WX8uA=
</data>
<key>Headers/QmsPlugin.h</key>
<data>
k1ElB+OnAYDlAZsTB5il2X25Zbo=
......@@ -23,6 +27,13 @@
</dict>
<key>files2</key>
<dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
aLAhYopvL1Lb4CTMG5A8x8RAmnkpXwHQbjsJdpq8gYg=
</data>
</dict>
<key>Headers/QmsPlugin.h</key>
<dict>
<key>hash2</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