Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
weihan-plugin
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Wei Han
weihan-plugin
Commits
9a381188
Commit
9a381188
authored
Oct 30, 2025
by
Wei Han
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
code update
parent
c8fb728f
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1098 additions
and
8 deletions
+1098
-8
QmsPluginFramework
...ios-arm64/QmsPluginFramework.framework/QmsPluginFramework
+0
-0
QmsPluginFramework
...simulator/QmsPluginFramework.framework/QmsPluginFramework
+0
-0
project.pbxproj
...ginFramework/QmsPluginFramework.xcodeproj/project.pbxproj
+0
-2
DashboardViewController.mm
...k/QmsPluginFramework/Dashboard/DashboardViewController.mm
+31
-6
IssueAPIClient.h
...PluginFramework/QmsPluginFramework/Issue/IssueAPIClient.h
+9
-0
IssueAPIClient.mm
...luginFramework/QmsPluginFramework/Issue/IssueAPIClient.mm
+80
-0
IssueDetailViewController.h
...work/QmsPluginFramework/Issue/IssueDetailViewController.h
+14
-0
IssueDetailViewController.mm
...ork/QmsPluginFramework/Issue/IssueDetailViewController.mm
+263
-0
IssuesViewController.h
...Framework/QmsPluginFramework/Issue/IssuesViewController.h
+11
-0
IssuesViewController.mm
...ramework/QmsPluginFramework/Issue/IssuesViewController.mm
+690
-0
QmsPluginFramework
...ios-arm64/QmsPluginFramework.framework/QmsPluginFramework
+0
-0
QmsPluginFramework
...simulator/QmsPluginFramework.framework/QmsPluginFramework
+0
-0
No files found.
framework/QmsPluginFramework/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/QmsPluginFramework
View file @
9a381188
No preview for this file type
framework/QmsPluginFramework/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/QmsPluginFramework
View file @
9a381188
No preview for this file type
framework/QmsPluginFramework/QmsPluginFramework.xcodeproj/project.pbxproj
View file @
9a381188
...
@@ -120,8 +120,6 @@
...
@@ -120,8 +120,6 @@
2F9647672E86307D002CC7CB
/* QmsPluginFramework */
,
2F9647672E86307D002CC7CB
/* QmsPluginFramework */
,
);
);
name
=
QmsPluginFramework
;
name
=
QmsPluginFramework
;
packageProductDependencies
=
(
);
productName
=
QmsPluginFramework
;
productName
=
QmsPluginFramework
;
productReference
=
2F9647652E86307D002CC7CB
/* QmsPluginFramework.framework */
;
productReference
=
2F9647652E86307D002CC7CB
/* QmsPluginFramework.framework */
;
productType
=
"com.apple.product-type.framework"
;
productType
=
"com.apple.product-type.framework"
;
...
...
framework/QmsPluginFramework/QmsPluginFramework/Dashboard/DashboardViewController.mm
View file @
9a381188
...
@@ -2,8 +2,11 @@
...
@@ -2,8 +2,11 @@
#import "DashboardViewController.h"
#import "DashboardViewController.h"
#import "DashboardHeaderView.h"
#import "DashboardHeaderView.h"
#import <CoreGraphics/CoreGraphics.h>
#import <CoreGraphics/CoreGraphics.h>
#import <objc/runtime.h>
#import "PlanViewController.h"
#import "PlanViewController.h"
#import "DashboardAPIClient.h"
#import "DashboardAPIClient.h"
#import "IssueDetailViewController.h"
#import "IssuesViewController.h"
@interface DashboardViewController ()
@interface DashboardViewController ()
...
@@ -345,7 +348,6 @@
...
@@ -345,7 +348,6 @@
- (void)handleInspectionBannerTap {
- (void)handleInspectionBannerTap {
NSLog(@"🟥 Inspection checklist banner tapped");
NSLog(@"🟥 Inspection checklist banner tapped");
// In future: open WebView screen or perform navigation
[UIView animateWithDuration:0.1 animations:^{
[UIView animateWithDuration:0.1 animations:^{
_inspectionBanner.alpha = 0.6;
_inspectionBanner.alpha = 0.6;
} completion:^(BOOL finished) {
} completion:^(BOOL finished) {
...
@@ -616,7 +618,7 @@
...
@@ -616,7 +618,7 @@
title.textColor = [UIColor blackColor];
title.textColor = [UIColor blackColor];
title.userInteractionEnabled = YES; // 👈 enable taps
title.userInteractionEnabled = YES; // 👈 enable taps
UITapGestureRecognizer *titleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(
section
HeaderTapped:)];
UITapGestureRecognizer *titleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(
issue
HeaderTapped:)];
[title addGestureRecognizer:titleTap];
[title addGestureRecognizer:titleTap];
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_green_arrowright"]];
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_green_arrowright"]];
arrow.frame = CGRectMake(self.view.bounds.size.width - 40, 2, 24, 24);
arrow.frame = CGRectMake(self.view.bounds.size.width - 40, 2, 24, 24);
...
@@ -846,6 +848,13 @@
...
@@ -846,6 +848,13 @@
infoLabel.attributedText = [self notificationTextForIssue:item];
infoLabel.attributedText = [self notificationTextForIssue:item];
[card addSubview:infoLabel];
[card addSubview:infoLabel];
// Keep a reference to its issue data
card.accessibilityValue = item[@"issue_reference"]; // for debugging
card.tag = self.issues.count; // optional index tagging
// Store the data for later use (simplest way)
objc_setAssociatedObject(card, "issueData", item, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// === Tap gesture ===
// === Tap gesture ===
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIssueTap:)];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIssueTap:)];
...
@@ -972,10 +981,19 @@
...
@@ -972,10 +981,19 @@
}
}
- (void)handleIssueTap:(UITapGestureRecognizer *)gesture {
- (void)handleIssueTap:(UITapGestureRecognizer *)gesture {
UIView *card = gesture.view;
UIView *tappedCard = gesture.view;
[self animateTapForView:card];
NSDictionary *issueData = objc_getAssociatedObject(tappedCard, "issueData");
NSLog(@"🪲 Issue card tapped");
// TODO: open IssueDetails later
if (issueData && [issueData isKindOfClass:[NSDictionary class]]) {
NSLog(@"🧭 Navigating to IssueDetailViewController with issue: %@", issueData);
IssueDetailViewController *nextVC = [[IssueDetailViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
nextVC.issueData = issueData;
[self presentViewController:nextVC animated:YES completion:nil];
} else {
NSLog(@"⚠️ No valid issue data found for tapped card: %@", issueData);
}
}
}
- (UIImage *)downloadImageFrom:(NSString *)urlString {
- (UIImage *)downloadImageFrom:(NSString *)urlString {
...
@@ -1013,6 +1031,13 @@
...
@@ -1013,6 +1031,13 @@
NSLog(@"📘 Section tapped: %@", header.text);
NSLog(@"📘 Section tapped: %@", header.text);
}
}
-(void)issueHeaderTapped:(UITapGestureRecognizer *)gesture {
IssuesViewController *nextVC = [[IssuesViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
}
- (void)cardTapped:(UITapGestureRecognizer *)gesture {
- (void)cardTapped:(UITapGestureRecognizer *)gesture {
UIView *card = gesture.view;
UIView *card = gesture.view;
[self animateTapForView:card];
[self animateTapForView:card];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Issue/IssueAPIClient.h
0 → 100644
View file @
9a381188
// IssueAPIClient.h
#import <Foundation/Foundation.h>
@interface
IssueAPIClient
:
NSObject
+
(
void
)
fetchIssues
:(
void
(
^
)(
NSDictionary
*
data
,
NSError
*
error
))
completion
;
@end
framework/QmsPluginFramework/QmsPluginFramework/Issue/IssueAPIClient.mm
0 → 100644
View file @
9a381188
// IssueAPIClient.mm
#import "IssueAPIClient.h"
@implementation IssueAPIClient
+ (void)fetchIssues:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/getIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
// ✅ JSON headers
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// ✅ Build JSON body
NSDictionary *deviceInfo = @{
@"OS": @"AND",
@"IMEI": @"AND:2a2f13ff17b1cbb5",
@"OS_VERSION": @"35",
@"MODEL": @"2306EPN60G",
@"APP_VERSION": @"1.22.26"
};
NSDictionary *jsonBody = @{
@"data": @{
@"os": @"AND",
@"drawing_plan_id": @"135",
@"information": deviceInfo
}
};
// ✅ Convert dictionary → NSData
NSError *jsonError;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
if (jsonError) {
NSLog(@"❌ JSON Serialization Error: %@", jsonError);
if (completion) completion(nil, jsonError);
return;
}
request.HTTPBody = bodyData;
// 🧭 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"📦 Body JSON: %@", jsonBody);
// ✅ Make request
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Network Error: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(nil, error);
});
return;
}
NSError *jsonParseError;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];
if (jsonParseError) {
NSLog(@"⚠️ JSON Parse Error: %@", jsonParseError);
} else {
NSLog(@"✅ Issues API Response:\n%@", jsonResponse);
}
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(jsonResponse, jsonParseError);
});
}];
[task resume];
}
@end
framework/QmsPluginFramework/QmsPluginFramework/Issue/IssueDetailViewController.h
0 → 100644
View file @
9a381188
// IssueDetailViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface
IssueDetailViewController
:
UIViewController
@property
(
nonatomic
,
strong
)
NSDictionary
*
issueData
;
@end
NS_ASSUME_NONNULL_END
framework/QmsPluginFramework/QmsPluginFramework/Issue/IssueDetailViewController.mm
0 → 100644
View file @
9a381188
#import "IssueDetailViewController.h"
@interface IssueDetailViewController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIImageView *issueImageView;
@property (nonatomic, strong) UILabel *refLabel;
@property (nonatomic, strong) UILabel *statusLabel;
@property (nonatomic, strong) UIView *detailsCard;
@property (nonatomic, strong) UIButton *editButton;
@property (nonatomic, strong) UIButton *deleteButton;
@end
@implementation IssueDetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor systemGray6Color];
self.title = @"Issue Details";
NSLog(@"📦 Received Issue Data: %@", self.issueData);
[self setupHeader];
[self setupScrollView];
[self setupHeaderImage];
[self setupDetailsCard];
[self setupButtons];
[self populateData];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UIView *headerView = [self.view viewWithTag:999];
if (!headerView) return;
// ✅ Use updated safe area now that it’s available
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
CGFloat topInset = safe.layoutFrame.origin.y;
CGFloat headerHeight = 56.0 + topInset;
headerView.frame = CGRectMake(0, 0, self.view.bounds.size.width, headerHeight);
// ✅ Adjust subviews within header
UIButton *backButton = [headerView viewWithTag:1];
UILabel *titleLabel = [headerView viewWithTag:2];
UIButton *historyButton = [headerView viewWithTag:3];
backButton.frame = CGRectMake(12, topInset + 8, 44, 40);
titleLabel.frame = CGRectMake(0, topInset + 8, self.view.bounds.size.width, 40);
historyButton.frame = CGRectMake(self.view.bounds.size.width - 56, topInset + 8, 44, 40);
// ✅ Shift scroll view below header
CGFloat headerBottom = CGRectGetMaxY(headerView.frame);
self.scrollView.frame = CGRectMake(0, headerBottom, self.view.bounds.size.width, self.view.bounds.size.height - headerBottom);
}
#pragma mark - Header Setup
- (void)setupHeader {
UIView *headerView = [[UIView alloc] init];
headerView.backgroundColor = [UIColor colorWithRed:0/255.0
green:109/255.0
blue:141/255.0
alpha:1.0];
headerView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
headerView.tag = 999;
[self.view addSubview:headerView];
// --- Back Button ---
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeSystem];
backButton.tag = 1;
[backButton setImage:[UIImage systemImageNamed:@"chevron.left"] forState:UIControlStateNormal];
backButton.tintColor = UIColor.whiteColor;
[backButton addTarget:self action:@selector(handleBackTap) forControlEvents:UIControlEventTouchUpInside];
[headerView addSubview:backButton];
// --- Title ---
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.tag = 2;
titleLabel.text = @"Issue Details";
titleLabel.font = [UIFont boldSystemFontOfSize:18];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.textColor = UIColor.whiteColor;
[headerView addSubview:titleLabel];
// --- History Button ---
UIButton *historyButton = [UIButton buttonWithType:UIButtonTypeSystem];
historyButton.tag = 3;
[historyButton setImage:[UIImage systemImageNamed:@"clock.arrow.circlepath"] forState:UIControlStateNormal];
historyButton.tintColor = UIColor.whiteColor;
[historyButton addTarget:self action:@selector(handleHistoryTap) forControlEvents:UIControlEventTouchUpInside];
[headerView addSubview:historyButton];
}
#pragma mark - UI Setup
- (void)setupScrollView {
UIView *headerView = [self.view viewWithTag:999];
CGFloat headerBottom = CGRectGetMaxY(headerView.frame);
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, headerBottom, self.view.bounds.size.width, self.view.bounds.size.height - headerBottom)];
self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.scrollView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.scrollView];
}
- (void)setupHeaderImage {
CGFloat imageHeight = 230;
self.issueImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, imageHeight)];
self.issueImageView.contentMode = UIViewContentModeScaleAspectFill;
self.issueImageView.clipsToBounds = YES;
self.issueImageView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
[self.scrollView addSubview:self.issueImageView];
}
- (void)setupDetailsCard {
CGFloat topOffset = 220;
CGFloat padding = 20;
self.detailsCard = [[UIView alloc] initWithFrame:CGRectMake(0, topOffset, self.view.bounds.size.width, 420)];
self.detailsCard.backgroundColor = UIColor.whiteColor;
self.detailsCard.layer.cornerRadius = 20;
self.detailsCard.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
[self.scrollView addSubview:self.detailsCard];
CGFloat y = 20;
// --- Reference ID ---
self.refLabel = [[UILabel alloc] initWithFrame:CGRectMake(padding, y, self.view.bounds.size.width - 2*padding, 24)];
self.refLabel.font = [UIFont boldSystemFontOfSize:20];
[self.detailsCard addSubview:self.refLabel];
y += 36;
// --- Status Capsule ---
self.statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(padding, y, 90, 28)];
self.statusLabel.font = [UIFont boldSystemFontOfSize:14];
self.statusLabel.textColor = UIColor.whiteColor;
self.statusLabel.textAlignment = NSTextAlignmentCenter;
self.statusLabel.layer.cornerRadius = 14;
self.statusLabel.layer.masksToBounds = YES;
[self.detailsCard addSubview:self.statusLabel];
y += 50;
// --- Info Labels ---
NSArray *titles = @[@"Created Date", @"Location", @"Category", @"Type", @"Issue", @"Created By", @"Comment"];
for (NSString *title in titles) {
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(padding, y, 120, 22)];
titleLabel.text = [NSString stringWithFormat:@"%@ :", title];
titleLabel.textColor = UIColor.darkGrayColor;
titleLabel.font = [UIFont systemFontOfSize:15];
[self.detailsCard addSubview:titleLabel];
UILabel *valueLabel = [[UILabel alloc] initWithFrame:CGRectMake(padding + 130, y, self.view.bounds.size.width - 150, 22)];
valueLabel.tag = [titles indexOfObject:title] + 100; // tag for later update
valueLabel.textColor = UIColor.blackColor;
valueLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
[self.detailsCard addSubview:valueLabel];
y += 28;
}
}
- (void)setupButtons {
CGFloat y = CGRectGetMaxY(self.detailsCard.frame) + 16;
CGFloat buttonWidth = (self.view.bounds.size.width - 60) / 2;
// --- Edit Button ---
self.editButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.editButton.frame = CGRectMake(20, y, buttonWidth, 44);
[self.editButton setTitle:@"Edit Issue" forState:UIControlStateNormal];
[self.editButton setTitleColor:[UIColor colorWithRed:0/255.0 green:115/255.0 blue:130/255.0 alpha:1] forState:UIControlStateNormal];
self.editButton.layer.cornerRadius = 8;
self.editButton.layer.borderWidth = 1;
self.editButton.layer.borderColor = [UIColor colorWithRed:0/255.0 green:115/255.0 blue:130/255.0 alpha:1].CGColor;
[self.scrollView addSubview:self.editButton];
// --- Delete Button ---
self.deleteButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.deleteButton.frame = CGRectMake(CGRectGetMaxX(self.editButton.frame) + 20, y, buttonWidth, 44);
[self.deleteButton setTitle:@"Delete Issue" forState:UIControlStateNormal];
[self.deleteButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
self.deleteButton.backgroundColor = [UIColor systemRedColor];
self.deleteButton.layer.cornerRadius = 8;
[self.scrollView addSubview:self.deleteButton];
CGFloat totalHeight = CGRectGetMaxY(self.deleteButton.frame) + 30;
self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, totalHeight);
}
#pragma mark - Data
- (void)populateData {
if (!self.issueData) return;
NSString *ref = self.issueData[@"issue_reference"] ?: @"Unknown Reference";
NSString *status = self.issueData[@"status_external"] ?: @"No Status";
NSString *statusColor = self.issueData[@"status_ex_color"] ?: @"#999999";
NSString *imageURL = self.issueData[@"image"];
// it is the correct spelling for self.issueData[@"creted_by"]
NSDictionary *createdBy = self.issueData[@"creted_by"];
NSArray *values = @[
self.issueData[@"created_at"] ?: @"–",
self.issueData[@"location_name"] ?: @"–",
self.issueData[@"category"] ?: @"–",
self.issueData[@"type"] ?: @"–",
self.issueData[@"issue"] ?: @"–",
createdBy[@"name"] ?: @"-",
self.issueData[@"remarks"] ?: @"–"
];
self.refLabel.text = ref;
self.statusLabel.text = status;
self.statusLabel.backgroundColor = [self colorFromHex:statusColor];
// Fill info rows
for (int i = 0; i < values.count; i++) {
UILabel *label = [self.detailsCard viewWithTag:100 + i];
if ([label isKindOfClass:[UILabel class]]) {
label.text = values[i];
}
}
// Load image
if (imageURL.length > 0) {
NSURL *url = [NSURL URLWithString:imageURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
self.issueImageView.image = img;
});
}
});
}
}
#pragma mark - Helper
- (UIColor *)colorFromHex:(NSString *)hexString {
if (!hexString || hexString.length < 6) return [UIColor grayColor];
unsigned int rgbValue = 0;
NSScanner *scanner = [NSScanner scannerWithString:hexString];
if ([hexString hasPrefix:@"#"]) scanner.scanLocation = 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];
}
#pragma mark - Button Actions
- (void)handleBackTap {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)handleHistoryTap {
NSLog(@"🕓 History tapped");
// TODO: Navigate to issue history screen later
}
@end
framework/QmsPluginFramework/QmsPluginFramework/Issue/IssuesViewController.h
0 → 100644
View file @
9a381188
// IssuesViewControlller.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface
IssuesViewController
:
UIViewController
@end
NS_ASSUME_NONNULL_END
framework/QmsPluginFramework/QmsPluginFramework/Issue/IssuesViewController.mm
0 → 100644
View file @
9a381188
// IssuesViewController.mm
#import "IssuesViewController.h"
#import "IssueAPIClient.h"
#import "IssueDetailViewController.h"
#pragma mark - Internal IssueCell
@interface IssueCell : UITableViewCell
@property (nonatomic, strong) UIImageView *thumbView;
@property (nonatomic, strong) UILabel *referenceLabel;
@property (nonatomic, strong) UILabel *statusLabel;
@property (nonatomic, strong) UILabel *locationLabel;
@property (nonatomic, strong) UILabel *createdLabel;
@property (nonatomic, strong) UILabel *detailsLabel;
@end
@implementation IssueCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.backgroundColor = UIColor.clearColor;
UIView *card = [[UIView alloc] init];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 12;
card.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.15].CGColor;
card.layer.shadowOpacity = 0.1;
card.layer.shadowRadius = 3;
card.layer.shadowOffset = CGSizeMake(0, 2);
card.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:card];
self.thumbView = [[UIImageView alloc] init];
self.thumbView.layer.cornerRadius = 22;
self.thumbView.clipsToBounds = YES;
self.thumbView.translatesAutoresizingMaskIntoConstraints = NO;
self.thumbView.contentMode = UIViewContentModeScaleAspectFill;
[card addSubview:self.thumbView];
self.referenceLabel = [[UILabel alloc] init];
self.referenceLabel.font = [UIFont boldSystemFontOfSize:15];
self.referenceLabel.textColor = [UIColor colorWithRed:0.0 green:0.45 blue:0.55 alpha:1];
self.referenceLabel.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:self.referenceLabel];
self.statusLabel = [[UILabel alloc] init];
self.statusLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold];
self.statusLabel.textColor = UIColor.whiteColor;
self.statusLabel.textAlignment = NSTextAlignmentCenter;
self.statusLabel.backgroundColor = [UIColor systemBlueColor];
self.statusLabel.layer.cornerRadius = 12;
self.statusLabel.clipsToBounds = YES;
self.statusLabel.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:self.statusLabel];
self.locationLabel = [[UILabel alloc] init];
self.locationLabel.font = [UIFont boldSystemFontOfSize:16];
self.locationLabel.textColor = UIColor.labelColor;
self.locationLabel.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:self.locationLabel];
self.createdLabel = [[UILabel alloc] init];
self.createdLabel.font = [UIFont systemFontOfSize:13];
self.createdLabel.textColor = [UIColor colorWithWhite:0.45 alpha:1];
self.createdLabel.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:self.createdLabel];
self.detailsLabel = [[UILabel alloc] init];
self.detailsLabel.font = [UIFont systemFontOfSize:14];
self.detailsLabel.textColor = UIColor.darkGrayColor;
self.detailsLabel.numberOfLines = 2;
self.detailsLabel.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:self.detailsLabel];
[NSLayoutConstraint activateConstraints:@[
[card.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:6],
[card.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[card.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[card.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-6],
[self.thumbView.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:12],
[self.thumbView.topAnchor constraintEqualToAnchor:card.topAnchor constant:12],
[self.thumbView.widthAnchor constraintEqualToConstant:44],
[self.thumbView.heightAnchor constraintEqualToConstant:44],
[self.statusLabel.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-12],
[self.statusLabel.topAnchor constraintEqualToAnchor:card.topAnchor constant:12],
[self.statusLabel.widthAnchor constraintEqualToConstant:70],
[self.statusLabel.heightAnchor constraintEqualToConstant:24],
[self.locationLabel.leadingAnchor constraintEqualToAnchor:self.thumbView.trailingAnchor constant:12],
[self.locationLabel.topAnchor constraintEqualToAnchor:card.topAnchor constant:12],
[self.createdLabel.leadingAnchor constraintEqualToAnchor:self.thumbView.trailingAnchor constant:12],
[self.createdLabel.topAnchor constraintEqualToAnchor:self.locationLabel.bottomAnchor constant:4],
[self.referenceLabel.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:12],
[self.referenceLabel.topAnchor constraintEqualToAnchor:self.thumbView.bottomAnchor constant:8],
[self.detailsLabel.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:12],
[self.detailsLabel.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-12],
[self.detailsLabel.topAnchor constraintEqualToAnchor:self.referenceLabel.bottomAnchor constant:4],
[self.detailsLabel.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-12],
]];
}
return self;
}
@end
@interface IssuesViewController () <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *withdrawButton;
@property (nonatomic, strong) UIButton *poaButton;
@property (nonatomic, strong) UIButton *filterButton;
@property (nonatomic, strong) UILabel *filterLabel;
@property (nonatomic, strong) UISearchBar *searchBar;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *issues;
@property (nonatomic, strong) NSDictionary *issueResponse;
@property (nonatomic, strong) NSString *selectedFilter;
@property (nonatomic, strong) NSArray<NSDictionary *> *groupedIssues;
@property (nonatomic, strong) NSString *searchText;
@property (nonatomic, strong) NSArray *filteredIssues;
@property (nonatomic, strong) NSArray *filteredGroupedIssues;
@end
@implementation IssuesViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor systemGroupedBackgroundColor];
[self setupHeader];
[self setupFilterBar];
[self setupSearchBar];
[self setupTableView];
self.tableView.sectionHeaderTopPadding = 0; // Optional, iOS 15+ removes top gap
self.tableView.sectionHeaderHeight = 34;
self.tableView.estimatedSectionHeaderHeight = 34;
[self setupConstraints];
[self handleGetIssue];
self.selectedFilter = @"All";
}
#pragma mark - Setup UI
- (void)setupHeader {
self.headerView = [[UIView alloc] init];
self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
self.headerView.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // THEME COLOR
[self.view addSubview:self.headerView];
// Back Button
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.left"] forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.backButton addTarget:self action:@selector(backTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:self.backButton];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Issues";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:20];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.titleLabel];
// Withdraw Button
self.withdrawButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.withdrawButton setImage:[UIImage systemImageNamed:@"tray.and.arrow.down"] forState:UIControlStateNormal];
self.withdrawButton.tintColor = UIColor.whiteColor;
self.withdrawButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.withdrawButton addTarget:self action:@selector(withdrawTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:self.withdrawButton];
// POA Button
self.poaButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.poaButton setImage:[UIImage systemImageNamed:@"doc.text"] forState:UIControlStateNormal];
self.poaButton.tintColor = UIColor.whiteColor;
self.poaButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.poaButton addTarget:self action:@selector(poaTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:self.poaButton];
}
- (void)setupFilterBar {
self.filterButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.filterButton.translatesAutoresizingMaskIntoConstraints = NO;
self.filterButton.layer.cornerRadius = 20;
self.filterButton.layer.borderWidth = 0.5;
self.filterButton.layer.borderColor = [UIColor lightGrayColor].CGColor;
[self.filterButton setTitle:@"Filter: All" forState:UIControlStateNormal];
self.filterButton.tintColor = UIColor.labelColor;
self.filterButton.backgroundColor = UIColor.whiteColor;
self.filterButton.titleLabel.font = [UIFont systemFontOfSize:15];
[self.filterButton addTarget:self action:@selector(filterTapped) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.filterButton];
}
- (void)setupSearchBar {
self.searchBar = [[UISearchBar alloc] init];
self.searchBar.placeholder = @"Search by reference...";
self.searchBar.translatesAutoresizingMaskIntoConstraints = NO;
self.searchBar.delegate = self;
self.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
self.searchBar.returnKeyType = UIReturnKeyDone;
[self.view addSubview:self.searchBar];
}
- (void)setupTableView {
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerClass:[IssueCell class] forCellReuseIdentifier:@"IssueCell"];
// 🔹 Section header behavior
if (@available(iOS 15.0, *)) {
self.tableView.sectionHeaderTopPadding = 0;
}
self.tableView.sectionHeaderHeight = 34;
self.tableView.estimatedSectionHeaderHeight = 34;
[self.view addSubview:self.tableView];
}
- (void)setupConstraints {
UILayoutGuide *guide = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:@[
// Header
[self.headerView.topAnchor constraintEqualToAnchor:guide.topAnchor],
[self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerView.heightAnchor constraintEqualToConstant:56],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:12],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.withdrawButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-56],
[self.withdrawButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.poaButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-12],
[self.poaButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
// Filter
[self.filterButton.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor constant:12],
[self.filterButton.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[self.filterButton.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[self.filterButton.heightAnchor constraintEqualToConstant:40],
// Search
[self.searchBar.topAnchor constraintEqualToAnchor:self.filterButton.bottomAnchor constant:8],
[self.searchBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.searchBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
// Table
[self.tableView.topAnchor constraintEqualToAnchor:self.searchBar.bottomAnchor constant:8],
[self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:guide.bottomAnchor],
]];
}
#pragma mark - Networking
- (void)handleGetIssue {
NSLog(@"🌐 Fetching Issue data...");
[IssueAPIClient fetchIssues:^(NSDictionary *data, NSError *error) {
if (error) {
NSLog(@"❌ Issue fetch failed: %@", error.localizedDescription);
return;
}
NSLog(@"✅ Raw Issue response received");
// Save response to Documents for inspection
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:nil];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"getIssue.txt"];
[jsonData writeToFile:filePath atomically:YES];
NSLog(@"📁 Issue saved to %@", filePath);
// 🔹 Save the full API response
self.issueResponse = data;
// 🔍 Parse JSON
NSDictionary *appData = data[@"AppData"];
if (![appData[@"status"] isEqualToString:@"success"]) {
NSLog(@"⚠️ Server returned non-success status: %@", appData[@"message"]);
return;
}
NSArray *mainData = data[@"Data"];
if (mainData.count == 0) {
NSLog(@"⚠️ No unit plan data found");
return;
}
// 🔹 Sort by date (latest first)
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"dd/MM/yyyy";
NSArray *sorted = [mainData sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
NSDate *dA = [formatter dateFromString:a[@"created_at"] ?: @""];
NSDate *dB = [formatter dateFromString:b[@"created_at"] ?: @""];
return [dB compare:dA]; // newest → oldest
}];
self.issues = sorted;
// ✅ Reload UI on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
NSLog(@"✅ Issues loaded: %lu", (unsigned long)self.issues.count);
}];
}
#pragma mark - search bar
- (void)applySearch:(NSString *)searchValue {
if (searchValue.length == 0) {
self.filteredIssues = nil;
self.filteredGroupedIssues = nil;
[self.tableView reloadData];
return;
}
NSString *search = [searchValue uppercaseString];
NSMutableArray *matches = [NSMutableArray array];
for (NSDictionary *issue in self.issues) {
NSString *reference = [issue[@"reference"] ?: @"" uppercaseString];
NSString *location = [issue[@"location_name"] ?: @"" uppercaseString];
NSString *remarks = [issue[@"remarks"] ?: @"" uppercaseString];
if ([reference containsString:search] ||
[location containsString:search] ||
[remarks containsString:search]) {
[matches addObject:issue];
}
}
// 🧩 If user is filtering by Status/Location/Date → regroup
if (![self.selectedFilter isEqualToString:@"All"]) {
NSMutableDictionary<NSString *, NSMutableArray *> *map = [NSMutableDictionary dictionary];
for (NSDictionary *issue in matches) {
NSString *key = @"";
if ([self.selectedFilter isEqualToString:@"Status"]) {
key = issue[@"status_external"] ?: @"(No Status)";
} else if ([self.selectedFilter isEqualToString:@"Location"]) {
key = issue[@"location_name"] ?: @"(No Location)";
} else if ([self.selectedFilter isEqualToString:@"Date"]) {
key = issue[@"created_at"] ?: @"(No Date)";
}
if (!map[key]) map[key] = [NSMutableArray array];
[map[key] addObject:issue];
}
NSArray *sortedKeys;
if ([self.selectedFilter isEqualToString:@"Date"]) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"dd/MM/yyyy";
sortedKeys = [[map allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
NSDate *dA = [formatter dateFromString:a];
NSDate *dB = [formatter dateFromString:b];
return [dB compare:dA]; // newest first
}];
} else {
sortedKeys = [[map allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}
NSMutableArray *grouped = [NSMutableArray array];
for (NSString *key in sortedKeys) {
[grouped addObject:@{ @"title": key, @"data": map[key] }];
}
self.filteredGroupedIssues = grouped;
self.filteredIssues = nil;
} else {
self.filteredIssues = matches;
self.filteredGroupedIssues = nil;
}
[self.tableView reloadData];
}
#pragma mark - Actions
- (void)backTapped {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)withdrawTapped {
NSLog(@"Withdraw button tapped");
}
- (void)poaTapped {
NSLog(@"POA button tapped");
}
# pragma mark - Filter
- (void)filterTapped {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Filter By"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
NSArray *filters = @[@"All", @"Status", @"Location", @"Date"];
for (NSString *filter in filters) {
UIAlertAction *action = [UIAlertAction actionWithTitle:filter
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self applyFilter:filter];
}];
[alert addAction:action];
}
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)applyFilter:(NSString *)filter {
self.selectedFilter = filter;
if ([filter isEqualToString:@"All"]) {
self.groupedIssues = @[]; // Flat mode
} else {
NSMutableArray *groups = [NSMutableArray array];
NSMutableDictionary<NSString *, NSMutableArray *> *map = [NSMutableDictionary dictionary];
for (NSDictionary *issue in self.issues) {
NSString *key = @"";
if ([filter isEqualToString:@"Status"]) {
key = issue[@"status_external"] ?: @"(No Status)";
} else if ([filter isEqualToString:@"Location"]) {
key = issue[@"location_name"] ?: @"(No Location)";
} else if ([filter isEqualToString:@"Date"]) {
key = issue[@"created_at"] ?: @"(No Date)";
}
if (!map[key]) {
map[key] = [NSMutableArray array];
}
[map[key] addObject:issue];
}
NSArray *sortedKeys;
if ([filter isEqualToString:@"Date"]) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"dd/MM/yyyy";
sortedKeys = [[map allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
NSDate *dA = [formatter dateFromString:a];
NSDate *dB = [formatter dateFromString:b];
return [dB compare:dA]; // newest → oldest
}];
} else if ([filter isEqualToString:@"Status"] || [filter isEqualToString:@"Location"]) {
sortedKeys = [[map allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
} else {
sortedKeys = [map allKeys];
}
for (NSString *key in sortedKeys) {
[groups addObject:@{
@"title": key,
@"data": map[key]
}];
}
self.groupedIssues = groups;
}
// 🔹 Update the filter button title dynamically
NSString *buttonTitle = [NSString stringWithFormat:@"Filter: %@", filter];
[self.filterButton setTitle:buttonTitle forState:UIControlStateNormal];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
- (UIColor *)colorFromHexString:(NSString *)hex {
unsigned int rgbValue = 0;
NSScanner *scanner = [NSScanner scannerWithString:hex];
if ([hex hasPrefix:@"#"]) [scanner setScanLocation:1];
[scanner scanHexInt:&rgbValue];
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0
green:((rgbValue & 0xFF00) >> 8) / 255.0
blue:(rgbValue & 0xFF) / 255.0
alpha:1.0];
}
#pragma mark - TableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// If user is searching:
if (self.filteredIssues || self.filteredGroupedIssues) {
if (self.filteredGroupedIssues) return self.filteredGroupedIssues.count;
return 1;
}
// Otherwise, normal mode
if ([self.selectedFilter isEqualToString:@"All"]) return 1;
return self.groupedIssues.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
// Hide default iOS header text (we use a custom header view below)
return nil;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.filteredIssues) return self.filteredIssues.count;
if (self.filteredGroupedIssues) {
NSArray *rows = self.filteredGroupedIssues[section][@"data"];
return rows.count;
}
if ([self.selectedFilter isEqualToString:@"All"]) return self.issues.count;
NSArray *rows = self.groupedIssues[section][@"data"];
return rows.count;
}
- (NSDictionary *)issueAtIndexPath:(NSIndexPath *)indexPath {
if (self.filteredIssues) return self.filteredIssues[indexPath.row];
if (self.filteredGroupedIssues) return self.filteredGroupedIssues[indexPath.section][@"data"][indexPath.row];
if ([self.selectedFilter isEqualToString:@"All"]) return self.issues[indexPath.row];
return self.groupedIssues[indexPath.section][@"data"][indexPath.row];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
IssueCell *cell = [tableView dequeueReusableCellWithIdentifier:@"IssueCell" forIndexPath:indexPath];
// --- Reset reused cell state ---
cell.thumbView.image = nil;
for (UIView *subview in cell.thumbView.subviews) {
[subview removeFromSuperview];
}
NSDictionary *issue = [self issueAtIndexPath:indexPath];
NSString *reference = issue[@"reference"] ?: @"";
NSString *location = issue[@"location_name"] ?: @"";
NSString *createdAt = issue[@"created_at"] ?: @"";
NSString *category = issue[@"category"] ?: @"";
NSString *type = issue[@"type"] ?: @"";
NSString *issueText = issue[@"issue"] ?: @"";
NSString *status = issue[@"status_external"] ?: @"";
NSString *statusColor = issue[@"status_ex_color"] ?: @"#CCCCCC";
NSString *thumbURL = issue[@"thumb_image"] ?: @"";
cell.referenceLabel.text = reference;
cell.locationLabel.text = location;
cell.createdLabel.text = [NSString stringWithFormat:@"Created on %@", createdAt];
cell.detailsLabel.text = [NSString stringWithFormat:@"%@ → %@ → %@", category, type, issueText];
cell.statusLabel.text = status;
cell.statusLabel.backgroundColor = [self colorFromHexString:statusColor];
// --- Thumbnail Handling ---
if (thumbURL.length > 0) {
NSURL *url = [NSURL URLWithString:thumbURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
IssueCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (!updateCell) return;
// Clear any "No image" labels
for (UIView *subview in updateCell.thumbView.subviews) {
[subview removeFromSuperview];
}
if (data) {
UIImage *img = [UIImage imageWithData:data];
if (img) {
updateCell.thumbView.image = img;
updateCell.thumbView.backgroundColor = UIColor.clearColor;
} else {
[self setNoImagePlaceholder:updateCell.thumbView];
}
} else {
[self setNoImagePlaceholder:updateCell.thumbView];
}
});
});
} else {
[self setNoImagePlaceholder:cell.thumbView];
}
return cell;
}
#pragma mark - Custom Section Header
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
NSArray *sourceGroups = self.filteredGroupedIssues ?: self.groupedIssues;
if ([self.selectedFilter isEqualToString:@"All"] && !self.filteredGroupedIssues) return nil;
if (section >= sourceGroups.count) return nil;
NSDictionary *group = sourceGroups[section];
NSString *title = group[@"title"] ?: @"";
UIView *headerView = [[UIView alloc] init];
headerView.backgroundColor = [UIColor colorWithWhite:0.94 alpha:1.0];
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.font = [UIFont boldSystemFontOfSize:17];
titleLabel.textColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // theme color
titleLabel.text = title.uppercaseString;
UIView *underline = [[UIView alloc] init];
underline.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:0.6];
underline.translatesAutoresizingMaskIntoConstraints = NO;
[headerView addSubview:titleLabel];
[headerView addSubview:underline];
[NSLayoutConstraint activateConstraints:@[
[titleLabel.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor constant:16],
[titleLabel.bottomAnchor constraintEqualToAnchor:headerView.bottomAnchor constant:-6],
[underline.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor constant:16],
[underline.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor constant:-16],
[underline.bottomAnchor constraintEqualToAnchor:headerView.bottomAnchor],
[underline.heightAnchor constraintEqualToConstant:1],
]];
return headerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if ([self.selectedFilter isEqualToString:@"All"] && !self.filteredGroupedIssues) return 0;
return 34;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 140;
}
#pragma mark - Helpers
- (void)setNoImagePlaceholder:(UIImageView *)thumbView {
// Clear any previous content
thumbView.image = nil;
thumbView.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
for (UIView *subview in thumbView.subviews) {
[subview removeFromSuperview];
}
UILabel *noImageLabel = [[UILabel alloc] init];
noImageLabel.text = @"No image";
noImageLabel.textAlignment = NSTextAlignmentCenter;
noImageLabel.font = [UIFont systemFontOfSize:10];
noImageLabel.textColor = [UIColor grayColor];
noImageLabel.translatesAutoresizingMaskIntoConstraints = NO;
[thumbView addSubview:noImageLabel];
[NSLayoutConstraint activateConstraints:@[
[noImageLabel.centerXAnchor constraintEqualToAnchor:thumbView.centerXAnchor],
[noImageLabel.centerYAnchor constraintEqualToAnchor:thumbView.centerYAnchor]
]];
}
#pragma mark - TableView Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *issue;
if ([self.selectedFilter isEqualToString:@"All"]) {
issue = self.issues[indexPath.row];
} else {
issue = self.groupedIssues[indexPath.section][@"data"][indexPath.row];
}
IssueDetailViewController *detailVC = [[IssueDetailViewController alloc] init];
detailVC.modalPresentationStyle = UIModalPresentationFullScreen;
detailVC.issueData = issue; // Pass the selected data
NSLog(@"🧭 Navigating to IssueDetailsViewController with issue: %@", issue);
[self presentViewController:detailVC animated:YES completion:nil];
}
@end
ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/QmsPluginFramework
View file @
9a381188
No preview for this file type
ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/QmsPluginFramework
View file @
9a381188
No preview for this file type
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment