@convep_mobilogy/react-native-qms-plugin 1.8.13 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/QmsPlugin.podspec +2 -4
  2. package/QmsPluginFramework.podspec +12 -0
  3. package/README.md +24 -8
  4. package/android/build.gradle +2 -1
  5. package/android/consumer-rules.pro +13 -0
  6. package/ios/QmsPlugin/QmsDashboardViewManager.mm +68 -26
  7. package/ios/QmsPlugin/QmsModule.mm +1293 -0
  8. package/ios/QmsPlugin/UIViewController+QmsAnalytics.m +3 -1
  9. package/lib/module/QmsDashboardView.android.js +121 -9
  10. package/lib/module/QmsDashboardView.android.js.map +1 -1
  11. package/lib/module/QmsPlugin.js +57 -0
  12. package/lib/module/QmsPlugin.js.map +1 -1
  13. package/lib/typescript/src/QmsDashboardView.android.d.ts.map +1 -1
  14. package/lib/typescript/src/QmsPlugin.d.ts +137 -0
  15. package/lib/typescript/src/QmsPlugin.d.ts.map +1 -1
  16. package/lib/typescript/src/index.d.ts +1 -1
  17. package/lib/typescript/src/index.d.ts.map +1 -1
  18. package/package.json +7 -4
  19. package/src/QmsDashboardView.android.tsx +156 -11
  20. package/src/QmsPlugin.ts +312 -0
  21. package/src/index.tsx +13 -1
  22. package/ios/QmsPluginFramework.xcframework/Info.plist +0 -44
  23. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Assets.car +0 -0
  24. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Headers/QmsPlugin.h +0 -43
  25. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Headers/QmsPluginFramework.h +0 -17
  26. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Info.plist +0 -0
  27. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Modules/module.modulemap +0 -6
  28. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Bold.ttf +0 -0
  29. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-BoldItalic.ttf +0 -0
  30. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-ExtraBold.ttf +0 -0
  31. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-ExtraLight.ttf +0 -0
  32. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Italic.ttf +0 -0
  33. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Light.ttf +0 -0
  34. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Medium.ttf +0 -0
  35. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Regular.ttf +0 -0
  36. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-SemiBold.ttf +0 -0
  37. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Thin.ttf +0 -0
  38. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/QmsPluginFramework +0 -0
  39. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/_CodeSignature/CodeResources +0 -289
  40. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/bm.json +0 -781
  41. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/cn.json +0 -782
  42. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/en.json +0 -782
  43. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Assets.car +0 -0
  44. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Headers/QmsPlugin.h +0 -43
  45. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Headers/QmsPluginFramework.h +0 -17
  46. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Info.plist +0 -0
  47. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Modules/module.modulemap +0 -6
  48. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Bold.ttf +0 -0
  49. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-BoldItalic.ttf +0 -0
  50. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-ExtraBold.ttf +0 -0
  51. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-ExtraLight.ttf +0 -0
  52. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Italic.ttf +0 -0
  53. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Light.ttf +0 -0
  54. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Medium.ttf +0 -0
  55. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Regular.ttf +0 -0
  56. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-SemiBold.ttf +0 -0
  57. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Thin.ttf +0 -0
  58. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/QmsPluginFramework +0 -0
  59. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/_CodeSignature/CodeResources +0 -289
  60. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/bm.json +0 -781
  61. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/cn.json +0 -782
  62. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/en.json +0 -782
@@ -73,6 +73,865 @@ RCT_EXPORT_MODULE(QmsModule)
73
73
  } @catch (__unused NSException *exception) {}
74
74
  }
75
75
 
76
+ - (NSString *)stringFromDictionary:(NSDictionary *)dictionary keys:(NSArray<NSString *> *)keys {
77
+ if (![dictionary isKindOfClass:[NSDictionary class]]) return @"";
78
+ for (NSString *key in keys) {
79
+ id value = dictionary[key];
80
+ if ((!value || value == [NSNull null]) && [dictionary[@"data"] isKindOfClass:[NSDictionary class]]) {
81
+ value = dictionary[@"data"][key];
82
+ }
83
+ if (value && value != [NSNull null]) {
84
+ NSString *text = [[value description] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
85
+ if (text.length > 0 && ![text.lowercaseString isEqualToString:@"null"]) return text;
86
+ }
87
+ }
88
+ return @"";
89
+ }
90
+
91
+ - (void)callAPIConfigSelector:(NSString *)selectorName stringValue:(NSString *)value {
92
+ if (selectorName.length == 0 || value.length == 0) return;
93
+ Class apiConfig = NSClassFromString(@"APIConfig");
94
+ SEL selector = NSSelectorFromString(selectorName);
95
+ if (!apiConfig || ![apiConfig respondsToSelector:selector]) return;
96
+
97
+ NSMethodSignature *signature = [apiConfig methodSignatureForSelector:selector];
98
+ if (!signature) return;
99
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
100
+ [invocation setSelector:selector];
101
+ [invocation setTarget:apiConfig];
102
+ NSString *argument = value;
103
+ [invocation setArgument:&argument atIndex:2];
104
+ [invocation invoke];
105
+ }
106
+
107
+ - (void)callAPIConfigSelector:(NSString *)selectorName boolValue:(BOOL)value {
108
+ Class apiConfig = NSClassFromString(@"APIConfig");
109
+ SEL selector = NSSelectorFromString(selectorName);
110
+ if (!apiConfig || ![apiConfig respondsToSelector:selector]) return;
111
+
112
+ NSMethodSignature *signature = [apiConfig methodSignatureForSelector:selector];
113
+ if (!signature) return;
114
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
115
+ [invocation setSelector:selector];
116
+ [invocation setTarget:apiConfig];
117
+ BOOL argument = value;
118
+ [invocation setArgument:&argument atIndex:2];
119
+ [invocation invoke];
120
+ }
121
+
122
+ - (void)configureAPIForAddIssueWithToken:(NSString *)userToken
123
+ clientID:(NSString *)clientID
124
+ clientCode:(NSString *)clientCode
125
+ isOrigin:(BOOL)isOrigin
126
+ options:(NSDictionary *)options {
127
+ [self callAPIConfigSelector:@"setAuthToken:" stringValue:userToken ?: @""];
128
+ [self callAPIConfigSelector:@"setClientId:" stringValue:clientID ?: @""];
129
+ [self callAPIConfigSelector:@"setClientCode:" stringValue:clientCode ?: @""];
130
+ [self callAPIConfigSelector:@"setIsOrigin:" boolValue:isOrigin];
131
+
132
+ NSString *projectId = [self stringFromDictionary:options keys:@[@"projectId", @"PROJECT_ID", @"project_id"]];
133
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
134
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
135
+ NSString *projectCode = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
136
+ NSString *userRole = [self stringFromDictionary:options keys:@[@"userRole", @"USER_ROLE", @"user_role", @"roleUser", @"ROLE_USER", @"role_user"]];
137
+ NSString *userId = [self stringFromDictionary:options keys:@[@"userId", @"USER_ID", @"user_id", @"roleUserId", @"ROLEUSER_ID", @"roleuser_id"]];
138
+ NSString *userName = [self stringFromDictionary:options keys:@[@"userName", @"USER_NAME", @"user_name"]];
139
+
140
+ [self callAPIConfigSelector:@"setProjectId:" stringValue:projectId];
141
+ [self callAPIConfigSelector:@"setDrawingPlanId:" stringValue:planId];
142
+ [self callAPIConfigSelector:@"setProjName:" stringValue:projectName];
143
+ [self callAPIConfigSelector:@"setProjCode:" stringValue:projectCode];
144
+ [self callAPIConfigSelector:@"setUserName:" stringValue:userName];
145
+
146
+ if (userRole.length > 0) [self callAPIConfigSelector:@"setUserRole:" stringValue:userRole];
147
+ if (userId.length > 0) [self callAPIConfigSelector:@"setUserId:" stringValue:userId];
148
+ }
149
+
150
+ - (void)fetchCompanyUrlForAddIssueWithToken:(NSString *)userToken
151
+ clientCode:(NSString *)clientCode
152
+ isOrigin:(BOOL)isOrigin
153
+ completion:(dispatch_block_t)completion {
154
+ if (clientCode.length == 0) {
155
+ completion();
156
+ return;
157
+ }
158
+
159
+ NSURL *url = [NSURL URLWithString:@"https://qms1.commudesk.com/api/framework/companycode/get"];
160
+ if (!url) {
161
+ completion();
162
+ return;
163
+ }
164
+
165
+ NSDictionary *data = @{
166
+ @"os": @"IOS",
167
+ @"imei": [[[UIDevice currentDevice] identifierForVendor] UUIDString] ?: @"IOS",
168
+ @"os_version": [[UIDevice currentDevice] systemVersion] ?: @"",
169
+ @"sdk_version": [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] ?: @"",
170
+ @"devices_model": [[UIDevice currentDevice] model] ?: @"",
171
+ @"is_origin": @(isOrigin ? 1 : 0),
172
+ @"company_code": clientCode ?: @""
173
+ };
174
+ NSDictionary *payload = @{@"data": data};
175
+ NSError *bodyError = nil;
176
+ NSData *body = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&bodyError];
177
+ if (!body || bodyError) {
178
+ completion();
179
+ return;
180
+ }
181
+
182
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
183
+ request.HTTPMethod = @"POST";
184
+ request.HTTPBody = body;
185
+ [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
186
+ [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
187
+ if (userToken.length > 0) {
188
+ [request setValue:[NSString stringWithFormat:@"Bearer %@", userToken] forHTTPHeaderField:@"Authorization"];
189
+ }
190
+
191
+ NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request
192
+ completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
193
+ if (!error && data.length > 0) {
194
+ NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
195
+ if ([json isKindOfClass:[NSDictionary class]]) {
196
+ NSDictionary *responseData = json[@"Data"] ?: json[@"data"];
197
+ NSString *urlPath = [responseData isKindOfClass:[NSDictionary class]] ? [responseData[@"url_path"] description] : @"";
198
+ if (urlPath.length > 0) {
199
+ NSString *baseURL = [urlPath stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
200
+ while ([baseURL hasSuffix:@"/"]) {
201
+ baseURL = [baseURL substringToIndex:baseURL.length - 1];
202
+ }
203
+ if (![baseURL hasSuffix:@"/api"]) {
204
+ baseURL = [baseURL stringByAppendingString:@"/api"];
205
+ }
206
+ [self callAPIConfigSelector:@"setBaseURL:" stringValue:baseURL];
207
+ }
208
+ }
209
+ }
210
+ dispatch_async(dispatch_get_main_queue(), completion);
211
+ }];
212
+ [task resume];
213
+ }
214
+
215
+ - (void)presentAddIssueWithOptions:(NSDictionary *)options
216
+ resolver:(RCTPromiseResolveBlock)resolve
217
+ rejecter:(RCTPromiseRejectBlock)reject {
218
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
219
+ if (planId.length == 0) {
220
+ reject(@"QMS_ADD_ISSUE_MISSING_DRAWING_PLAN_ID", @"drawingPlanId/drawing_plan_id/plan_id is required to open Add Issue directly.", nil);
221
+ return;
222
+ }
223
+
224
+ UIViewController *presenter = [self topViewController];
225
+ if (!presenter) {
226
+ reject(@"QMS_ADD_ISSUE_NO_PRESENTER", @"Unable to find a view controller to present Add Issue.", nil);
227
+ return;
228
+ }
229
+
230
+ Class planClass = NSClassFromString(@"PlanViewController");
231
+ if (!planClass) {
232
+ reject(@"QMS_ADD_ISSUE_UNAVAILABLE", @"QMS Add Issue screen is not available.", nil);
233
+ return;
234
+ }
235
+
236
+ UIViewController *planVC = [[planClass alloc] init];
237
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
238
+ NSString *projectCode = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
239
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name", @"draw_plan_name"]];
240
+ NSString *issueLimit = [self stringFromDictionary:options keys:@[@"issueLimit", @"ISSUE_LIMIT", @"issue_limit"]];
241
+ NSString *issueLimitWarning = [self stringFromDictionary:options keys:@[@"issueLimitWarningText", @"ISSUE_LIMIT_WARNING_TEXT", @"issue_limit_warning_text"]];
242
+ NSString *draftCount = [self stringFromDictionary:options keys:@[@"draftCount", @"DRAFT_COUNT", @"draft_count"]];
243
+ NSString *userName = [self stringFromDictionary:options keys:@[@"userName", @"USER_NAME", @"user_name"]];
244
+
245
+ @try {
246
+ [planVC setValue:planId forKey:@"planId"];
247
+ if (unitName.length > 0) [planVC setValue:unitName forKey:@"unitName"];
248
+ if (projectName.length > 0) [planVC setValue:projectName forKey:@"projectName"];
249
+ if (projectCode.length > 0) [planVC setValue:projectCode forKey:@"projectCode"];
250
+ if (issueLimit.length > 0) [planVC setValue:@(issueLimit.integerValue) forKey:@"issueLimit"];
251
+ if (issueLimitWarning.length > 0) [planVC setValue:issueLimitWarning forKey:@"issueLimitWarning"];
252
+ if (draftCount.length > 0) [planVC setValue:@(draftCount.integerValue) forKey:@"draftCount"];
253
+ if (userName.length > 0) [planVC setValue:userName forKey:@"userName"];
254
+ } @catch (__unused NSException *exception) {}
255
+
256
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
257
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
258
+ @try {
259
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
260
+ planVC.modalPresentationStyle = UIModalPresentationCustom;
261
+ planVC.transitioningDelegate = delegate;
262
+ } @catch (__unused NSException *exception) {
263
+ planVC.modalPresentationStyle = UIModalPresentationFullScreen;
264
+ }
265
+ } else {
266
+ planVC.modalPresentationStyle = UIModalPresentationFullScreen;
267
+ }
268
+
269
+ [presenter presentViewController:planVC animated:YES completion:^{
270
+ resolve(@YES);
271
+ }];
272
+ }
273
+
274
+ - (void)presentPropertyInfoWithOptions:(NSDictionary *)options
275
+ resolver:(RCTPromiseResolveBlock)resolve
276
+ rejecter:(RCTPromiseRejectBlock)reject {
277
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
278
+ if (planId.length == 0) {
279
+ reject(@"QMS_PROPERTY_INFO_MISSING_PLAN_ID", @"drawingPlanId/drawing_plan_id/plan_id is required to open Property Info directly.", nil);
280
+ return;
281
+ }
282
+
283
+ UIViewController *presenter = [self topViewController];
284
+ if (!presenter) {
285
+ reject(@"QMS_PROPERTY_INFO_NO_PRESENTER", @"Unable to find a view controller to present Property Info.", nil);
286
+ return;
287
+ }
288
+
289
+ Class vcClass = NSClassFromString(@"GeneralInfoViewController");
290
+ if (!vcClass) {
291
+ reject(@"QMS_PROPERTY_INFO_UNAVAILABLE", @"QMS Property Info screen is not available.", nil);
292
+ return;
293
+ }
294
+
295
+ UIViewController *vc = [[vcClass alloc] init];
296
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name", @"draw_plan_name"]];
297
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
298
+ NSString *projectId = [self stringFromDictionary:options keys:@[@"projectId", @"PROJECT_ID", @"project_id"]];
299
+
300
+ @try {
301
+ [vc setValue:planId forKey:@"planId"];
302
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"unitName"];
303
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
304
+ if (projectId.length > 0) [vc setValue:@(projectId.integerValue) forKey:@"projectId"];
305
+ } @catch (__unused NSException *exception) {}
306
+
307
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
308
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
309
+ @try {
310
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
311
+ vc.modalPresentationStyle = UIModalPresentationCustom;
312
+ vc.transitioningDelegate = delegate;
313
+ } @catch (__unused NSException *exception) {
314
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
315
+ }
316
+ } else {
317
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
318
+ }
319
+
320
+ [presenter presentViewController:vc animated:YES completion:^{
321
+ resolve(@YES);
322
+ }];
323
+ }
324
+
325
+ - (void)presentAppointmentListWithOptions:(NSDictionary *)options
326
+ resolver:(RCTPromiseResolveBlock)resolve
327
+ rejecter:(RCTPromiseRejectBlock)reject {
328
+ UIViewController *presenter = [self topViewController];
329
+ if (!presenter) {
330
+ reject(@"QMS_APPT_LIST_NO_PRESENTER", @"Unable to find a view controller to present Appointment List.", nil);
331
+ return;
332
+ }
333
+
334
+ Class vcClass = NSClassFromString(@"AppointmentViewMoreViewController");
335
+ if (!vcClass) {
336
+ reject(@"QMS_APPT_LIST_UNAVAILABLE", @"QMS Appointment List screen is not available.", nil);
337
+ return;
338
+ }
339
+
340
+ UIViewController *vc = [[vcClass alloc] init];
341
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
342
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
343
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
344
+
345
+ // AppointmentViewMoreViewController uses projectCode for the unit name
346
+ @try {
347
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
348
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"projectCode"];
349
+ if (planId.length > 0) [self callAPIConfigSelector:@"setDrawingPlanId:" stringValue:planId];
350
+ } @catch (__unused NSException *exception) {}
351
+
352
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
353
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
354
+ @try {
355
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
356
+ vc.modalPresentationStyle = UIModalPresentationCustom;
357
+ vc.transitioningDelegate = delegate;
358
+ } @catch (__unused NSException *exception) {
359
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
360
+ }
361
+ } else {
362
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
363
+ }
364
+
365
+ [presenter presentViewController:vc animated:YES completion:^{
366
+ resolve(@YES);
367
+ }];
368
+ }
369
+
370
+ - (void)presentMakeAppointmentWithOptions:(NSDictionary *)options
371
+ resolver:(RCTPromiseResolveBlock)resolve
372
+ rejecter:(RCTPromiseRejectBlock)reject {
373
+ UIViewController *presenter = [self topViewController];
374
+ if (!presenter) {
375
+ reject(@"QMS_MAKE_APPT_NO_PRESENTER", @"Unable to find a view controller to present Make Appointment.", nil);
376
+ return;
377
+ }
378
+
379
+ Class vcClass = NSClassFromString(@"AppointmentViewController");
380
+ if (!vcClass) {
381
+ reject(@"QMS_MAKE_APPT_UNAVAILABLE", @"QMS Make Appointment screen is not available.", nil);
382
+ return;
383
+ }
384
+
385
+ UIViewController *vc = [[vcClass alloc] init];
386
+ NSString *projectId = [self stringFromDictionary:options keys:@[@"projectId", @"PROJECT_ID", @"project_id"]];
387
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
388
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
389
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
390
+
391
+ @try {
392
+ if (projectId.length > 0) [vc setValue:projectId forKey:@"projectId"];
393
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
394
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"projectCode"];
395
+ if (planId.length > 0) [self callAPIConfigSelector:@"setDrawingPlanId:" stringValue:planId];
396
+ } @catch (__unused NSException *exception) {}
397
+
398
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
399
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
400
+ @try {
401
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
402
+ vc.modalPresentationStyle = UIModalPresentationCustom;
403
+ vc.transitioningDelegate = delegate;
404
+ } @catch (__unused NSException *exception) {
405
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
406
+ }
407
+ } else {
408
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
409
+ }
410
+
411
+ [presenter presentViewController:vc animated:YES completion:^{
412
+ resolve(@YES);
413
+ }];
414
+ }
415
+
416
+ - (void)presentPoaSignOffWithOptions:(NSDictionary *)options
417
+ resolver:(RCTPromiseResolveBlock)resolve
418
+ rejecter:(RCTPromiseRejectBlock)reject {
419
+ UIViewController *presenter = [self topViewController];
420
+ if (!presenter) {
421
+ reject(@"QMS_POA_NO_PRESENTER", @"Unable to find a view controller to present POA Sign-Off.", nil);
422
+ return;
423
+ }
424
+
425
+ Class vcClass = NSClassFromString(@"POAViewController");
426
+ if (!vcClass) {
427
+ reject(@"QMS_POA_UNAVAILABLE", @"QMS POA Sign-Off screen is not available.", nil);
428
+ return;
429
+ }
430
+
431
+ UIViewController *vc = [[vcClass alloc] init];
432
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
433
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
434
+ NSString *tnc = [self stringFromDictionary:options keys:@[@"tnc", @"TNC", @"terms_and_condition"]];
435
+
436
+ @try {
437
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
438
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"projectCode"];
439
+ if (tnc.length > 0) [vc setValue:tnc forKey:@"tnc"];
440
+ } @catch (__unused NSException *exception) {}
441
+
442
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
443
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
444
+ @try {
445
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
446
+ vc.modalPresentationStyle = UIModalPresentationCustom;
447
+ vc.transitioningDelegate = delegate;
448
+ } @catch (__unused NSException *exception) {
449
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
450
+ }
451
+ } else {
452
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
453
+ }
454
+
455
+ [presenter presentViewController:vc animated:YES completion:^{
456
+ resolve(@YES);
457
+ }];
458
+ }
459
+
460
+ - (void)presentIssueListWithOptions:(NSDictionary *)options
461
+ resolver:(RCTPromiseResolveBlock)resolve
462
+ rejecter:(RCTPromiseRejectBlock)reject {
463
+ UIViewController *presenter = [self topViewController];
464
+ if (!presenter) {
465
+ reject(@"QMS_ISSUE_LIST_NO_PRESENTER", @"Unable to find a view controller to present Issue List.", nil);
466
+ return;
467
+ }
468
+
469
+ Class vcClass = NSClassFromString(@"IssuesViewController");
470
+ if (!vcClass) {
471
+ reject(@"QMS_ISSUE_LIST_UNAVAILABLE", @"QMS Issue List screen is not available.", nil);
472
+ return;
473
+ }
474
+
475
+ UIViewController *vc = [[vcClass alloc] init];
476
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
477
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
478
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
479
+ NSString *headerTitle = [self stringFromDictionary:options keys:@[@"headerTitle", @"HEADER_TITLE", @"header_title"]];
480
+
481
+ // IssuesViewController uses projectCode for the unit/draw-plan name and
482
+ // projectName for the project name. The drawing plan id flows via APIConfig.
483
+ @try {
484
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
485
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"projectCode"];
486
+ if (planId.length > 0) [self callAPIConfigSelector:@"setDrawingPlanId:" stringValue:planId];
487
+ if (headerTitle.length > 0) [vc setValue:headerTitle forKey:@"headerTitle"];
488
+
489
+ // roa flag — accept "1"/1 as true, anything else as false
490
+ id roaRaw = options[@"roa"] ?: options[@"ROA"];
491
+ BOOL roaBln = [roaRaw isKindOfClass:[NSNumber class]] ? [roaRaw isEqualToNumber:@1] :
492
+ [roaRaw isKindOfClass:[NSString class]] ? [roaRaw isEqualToString:@"1"] : NO;
493
+ [self callAPIConfigSelector:@"setIsRoaBln:" boolValue:roaBln];
494
+ [vc setValue:@(roaBln) forKey:@"roaBln"];
495
+
496
+ // multiZone flag — accept YES/true/1/"1" as true
497
+ id multiZoneRaw = options[@"multiZone"] ?: options[@"MULTI_ZONE"] ?: options[@"multi_zone"];
498
+ NSLog(@"🔍 [openIssueList] multiZone raw value: %@ (class: %@)", multiZoneRaw, NSStringFromClass([multiZoneRaw class]));
499
+ BOOL multiZoneBln = [multiZoneRaw isKindOfClass:[NSNumber class]] ? [multiZoneRaw boolValue] :
500
+ [multiZoneRaw isKindOfClass:[NSString class]] ? [multiZoneRaw isEqualToString:@"1"] : NO;
501
+ NSLog(@"🔍 [openIssueList] multiZone resolved BOOL: %@", multiZoneBln ? @"YES" : @"NO");
502
+ [self callAPIConfigSelector:@"setIsMulti:" boolValue:multiZoneBln];
503
+ NSLog(@"🔍 [openIssueList] APIConfig.isMulti after set: %@", [NSClassFromString(@"APIConfig") valueForKey:@"isMulti"] ? @"YES" : @"NO");
504
+ [vc setValue:@(multiZoneBln) forKey:@"isMulti"];
505
+ } @catch (__unused NSException *exception) {
506
+ NSLog(@"⚠️ [openIssueList] exception while setting multiZone: %@", exception);
507
+ }
508
+
509
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
510
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
511
+ @try {
512
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
513
+ vc.modalPresentationStyle = UIModalPresentationCustom;
514
+ vc.transitioningDelegate = delegate;
515
+ } @catch (__unused NSException *exception) {
516
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
517
+ }
518
+ } else {
519
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
520
+ }
521
+
522
+ [presenter presentViewController:vc animated:YES completion:^{
523
+ resolve(@YES);
524
+ }];
525
+ }
526
+
527
+ - (void)presentAnnouncementWithOptions:(NSDictionary *)options
528
+ resolver:(RCTPromiseResolveBlock)resolve
529
+ rejecter:(RCTPromiseRejectBlock)reject {
530
+ UIViewController *presenter = [self topViewController];
531
+ if (!presenter) {
532
+ reject(@"QMS_ANNOUNCEMENT_NO_PRESENTER", @"Unable to find a view controller to present Announcement.", nil);
533
+ return;
534
+ }
535
+
536
+ Class vcClass = NSClassFromString(@"AnnouncementViewController");
537
+ if (!vcClass) {
538
+ reject(@"QMS_ANNOUNCEMENT_UNAVAILABLE", @"QMS Announcement screen is not available.", nil);
539
+ return;
540
+ }
541
+
542
+ UIViewController *vc = [[vcClass alloc] init];
543
+ NSString *projectId = [self stringFromDictionary:options keys:@[@"projectId", @"PROJECT_ID", @"project_id"]];
544
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
545
+ NSString *listTitle = [self stringFromDictionary:options keys:@[@"announcementTitle", @"ANNOUNCEMENT_TITLE", @"announcement_title"]];
546
+ NSString *detailTitle = [self stringFromDictionary:options keys:@[@"announcementDetailTitle", @"ANNOUNCEMENT_DETAIL_TITLE", @"announcement_detail_title"]];
547
+
548
+ @try {
549
+ if (projectId.length > 0) [self callAPIConfigSelector:@"setProjectId:" stringValue:projectId];
550
+ if (projectName.length > 0) [self callAPIConfigSelector:@"setProjName:" stringValue:projectName];
551
+ if (listTitle.length > 0) [vc setValue:listTitle forKey:@"screenTitle"];
552
+ if (detailTitle.length > 0) [vc setValue:detailTitle forKey:@"detailScreenTitle"];
553
+ } @catch (__unused NSException *exception) {}
554
+
555
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
556
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
557
+ @try {
558
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
559
+ vc.modalPresentationStyle = UIModalPresentationCustom;
560
+ vc.transitioningDelegate = delegate;
561
+ } @catch (__unused NSException *exception) {
562
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
563
+ }
564
+ } else {
565
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
566
+ }
567
+
568
+ [presenter presentViewController:vc animated:YES completion:^{
569
+ resolve(@YES);
570
+ }];
571
+ }
572
+
573
+ - (void)presentAnnouncementDetailsWithOptions:(NSDictionary *)options
574
+ resolver:(RCTPromiseResolveBlock)resolve
575
+ rejecter:(RCTPromiseRejectBlock)reject {
576
+ UIViewController *presenter = [self topViewController];
577
+ if (!presenter) {
578
+ reject(@"QMS_ANNOUNCEMENT_DETAILS_NO_PRESENTER", @"Unable to find a view controller to present Announcement Details.", nil);
579
+ return;
580
+ }
581
+
582
+ Class vcClass = NSClassFromString(@"AnnouncementDetailsViewController");
583
+ NSLog(@"[QmsModule] AnnouncementDetailsViewController class lookup: %@", vcClass ? NSStringFromClass(vcClass) : @"NIL — class not found in binary");
584
+ if (!vcClass) {
585
+ NSLog(@"[QmsModule] AnnouncementDetailsViewController not found — falling back to RN screen");
586
+ reject(@"QMS_ANNOUNCEMENT_DETAILS_UNAVAILABLE", @"QMS Announcement Details screen is not available.", nil);
587
+ return;
588
+ }
589
+
590
+ UIViewController *vc = [[vcClass alloc] init];
591
+
592
+ // Build the data dict that AnnouncementDetailsViewController.data expects.
593
+ // The JS caller passes the announcement fields as top-level keys in options.
594
+ NSMutableDictionary *announcementData = [NSMutableDictionary dictionary];
595
+ NSArray *dataKeys = @[@"id", @"title", @"description", @"start_date", @"end_date",
596
+ @"created_at", @"image", @"cover", @"popup", @"date"];
597
+ for (NSString *key in dataKeys) {
598
+ id value = options[key];
599
+ if (value && value != [NSNull null]) {
600
+ announcementData[key] = value;
601
+ }
602
+ }
603
+
604
+ NSString *screenTitle = [self stringFromDictionary:options
605
+ keys:@[@"announcementDetailTitle",
606
+ @"ANNOUNCEMENT_DETAIL_TITLE",
607
+ @"announcement_detail_title"]];
608
+
609
+ @try {
610
+ [vc setValue:[announcementData copy] forKey:@"data"];
611
+ // "from" is used by the details VC to choose the date key ("start_date" vs "date")
612
+ [vc setValue:@"announcement" forKey:@"from"];
613
+ if (screenTitle.length > 0) [vc setValue:screenTitle forKey:@"screenTitle"];
614
+ } @catch (__unused NSException *exception) {}
615
+
616
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
617
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
618
+ @try {
619
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
620
+ vc.modalPresentationStyle = UIModalPresentationCustom;
621
+ vc.transitioningDelegate = delegate;
622
+ } @catch (__unused NSException *exception) {
623
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
624
+ }
625
+ } else {
626
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
627
+ }
628
+
629
+ [presenter presentViewController:vc animated:YES completion:^{
630
+ resolve(@YES);
631
+ }];
632
+ }
633
+
634
+ - (void)presentProjectUpdateWithOptions:(NSDictionary *)options
635
+ resolver:(RCTPromiseResolveBlock)resolve
636
+ rejecter:(RCTPromiseRejectBlock)reject {
637
+ UIViewController *presenter = [self topViewController];
638
+ if (!presenter) {
639
+ reject(@"QMS_PROJECT_UPDATE_NO_PRESENTER", @"Unable to find a view controller to present Community Notice.", nil);
640
+ return;
641
+ }
642
+
643
+ // Prefer project-specific announcement screen; fall back to client-level one.
644
+ // Class vcClass = NSClassFromString(@"AnnouncementViewController");
645
+ Class vcClass = NSClassFromString(@"ClientAnnouncementViewController");
646
+ // if (!vcClass) {
647
+ // vcClass = NSClassFromString(@"ClientAnnouncementViewController");
648
+
649
+ // }
650
+ if (!vcClass) {
651
+ reject(@"QMS_PROJECT_UPDATE_UNAVAILABLE", @"QMS Community Notice screen is not available.", nil);
652
+ return;
653
+ }
654
+
655
+ UIViewController *vc = [[vcClass alloc] init];
656
+ NSString *projectId = [self stringFromDictionary:options keys:@[@"projectId", @"PROJECT_ID", @"project_id"]];
657
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
658
+ NSString *listTitle = [self stringFromDictionary:options keys:@[@"projectUpdateTitle", @"PROJECT_UPDATE_TITLE", @"project_update_title"]];
659
+ NSString *detailTitle = [self stringFromDictionary:options keys:@[@"projectUpdateDetailTitle", @"PROJECT_UPDATE_DETAIL_TITLE", @"project_update_detail_title"]];
660
+
661
+ @try {
662
+ if (projectId.length > 0) [self callAPIConfigSelector:@"setProjectId:" stringValue:projectId];
663
+ if (projectName.length > 0) [self callAPIConfigSelector:@"setProjName:" stringValue:projectName];
664
+ // Pass custom screen titles supplied by the JS caller (e.g. "Community Notice").
665
+ if (listTitle.length > 0) [vc setValue:listTitle forKey:@"screenTitle"];
666
+ if (detailTitle.length > 0) [vc setValue:detailTitle forKey:@"detailScreenTitle"];
667
+ } @catch (__unused NSException *exception) {}
668
+
669
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
670
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
671
+ @try {
672
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
673
+ vc.modalPresentationStyle = UIModalPresentationCustom;
674
+ vc.transitioningDelegate = delegate;
675
+ } @catch (__unused NSException *exception) {
676
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
677
+ }
678
+ } else {
679
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
680
+ }
681
+
682
+ [presenter presentViewController:vc animated:YES completion:^{
683
+ resolve(@YES);
684
+ }];
685
+ }
686
+
687
+ - (void)presentAccessItemWithOptions:(NSDictionary *)options
688
+ resolver:(RCTPromiseResolveBlock)resolve
689
+ rejecter:(RCTPromiseRejectBlock)reject {
690
+ UIViewController *presenter = [self topViewController];
691
+ if (!presenter) {
692
+ reject(@"QMS_ACCESS_ITEM_NO_PRESENTER", @"Unable to find a view controller to present Access Item.", nil);
693
+ return;
694
+ }
695
+
696
+ Class vcClass = NSClassFromString(@"AccessItemViewController");
697
+ if (!vcClass) {
698
+ reject(@"QMS_ACCESS_ITEM_UNAVAILABLE", @"QMS Access Item screen is not available.", nil);
699
+ return;
700
+ }
701
+
702
+ UIViewController *vc = [[vcClass alloc] init];
703
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
704
+ NSString *projectCode = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
705
+
706
+ @try {
707
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
708
+ if (projectCode.length > 0) [vc setValue:projectCode forKey:@"projectCode"];
709
+ } @catch (__unused NSException *exception) {}
710
+
711
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
712
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
713
+ @try {
714
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
715
+ vc.modalPresentationStyle = UIModalPresentationCustom;
716
+ vc.transitioningDelegate = delegate;
717
+ } @catch (__unused NSException *exception) {
718
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
719
+ }
720
+ } else {
721
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
722
+ }
723
+
724
+ [presenter presentViewController:vc animated:YES completion:^{
725
+ resolve(@YES);
726
+ }];
727
+ }
728
+
729
+ - (void)presentInboxWithOptions:(NSDictionary *)options
730
+ resolver:(RCTPromiseResolveBlock)resolve
731
+ rejecter:(RCTPromiseRejectBlock)reject {
732
+ UIViewController *presenter = [self topViewController];
733
+ if (!presenter) {
734
+ reject(@"QMS_INBOX_NO_PRESENTER", @"Unable to find a view controller to present Inbox.", nil);
735
+ return;
736
+ }
737
+
738
+ Class vcClass = NSClassFromString(@"NotificationViewController");
739
+ if (!vcClass) {
740
+ reject(@"QMS_INBOX_UNAVAILABLE", @"QMS Inbox screen is not available.", nil);
741
+ return;
742
+ }
743
+
744
+ UIViewController *vc = [[vcClass alloc] init];
745
+ NSString *projectId = [self stringFromDictionary:options keys:@[@"projectId", @"PROJECT_ID", @"project_id"]];
746
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
747
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
748
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
749
+
750
+ @try {
751
+ if (projectId.length > 0) [vc setValue:@(projectId.integerValue) forKey:@"projectId"];
752
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
753
+ if (planId.length > 0) [vc setValue:planId forKey:@"planId"];
754
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"unitName"];
755
+ } @catch (__unused NSException *exception) {}
756
+
757
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
758
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
759
+ @try {
760
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
761
+ vc.modalPresentationStyle = UIModalPresentationCustom;
762
+ vc.transitioningDelegate = delegate;
763
+ } @catch (__unused NSException *exception) {
764
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
765
+ }
766
+ } else {
767
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
768
+ }
769
+
770
+ [presenter presentViewController:vc animated:YES completion:^{
771
+ resolve(@YES);
772
+ }];
773
+ }
774
+
775
+ - (void)presentAppointmentDetailsWithOptions:(NSDictionary *)options
776
+ resolver:(RCTPromiseResolveBlock)resolve
777
+ rejecter:(RCTPromiseRejectBlock)reject {
778
+ UIViewController *presenter = [self topViewController];
779
+ if (!presenter) {
780
+ reject(@"QMS_APPT_DETAILS_NO_PRESENTER", @"Unable to find a view controller to present Appointment Details.", nil);
781
+ return;
782
+ }
783
+
784
+ Class vcClass = NSClassFromString(@"AppointmentDetailsViewController");
785
+ if (!vcClass) {
786
+ reject(@"QMS_APPT_DETAILS_UNAVAILABLE", @"QMS Appointment Details screen is not available.", nil);
787
+ return;
788
+ }
789
+
790
+ UIViewController *vc = [[vcClass alloc] init];
791
+
792
+ NSString *appointmentIdStr = [self stringFromDictionary:options
793
+ keys:@[@"appointmentId", @"appointment_id", @"APPOINTMENT_ID"]];
794
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
795
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
796
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
797
+
798
+ @try {
799
+ if (appointmentIdStr.length > 0) {
800
+ [vc setValue:@(appointmentIdStr.integerValue) forKey:@"appointmentId"];
801
+ }
802
+ if (planId.length > 0) [self callAPIConfigSelector:@"setDrawingPlanId:" stringValue:planId];
803
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
804
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"projectCode"];
805
+ } @catch (__unused NSException *exception) {}
806
+
807
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
808
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
809
+ @try {
810
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
811
+ vc.modalPresentationStyle = UIModalPresentationCustom;
812
+ vc.transitioningDelegate = delegate;
813
+ } @catch (__unused NSException *exception) {
814
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
815
+ }
816
+ } else {
817
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
818
+ }
819
+
820
+ [presenter presentViewController:vc animated:YES completion:^{
821
+ resolve(@YES);
822
+ }];
823
+ }
824
+
825
+ - (void)presentIssueDetailsWithOptions:(NSDictionary *)options
826
+ resolver:(RCTPromiseResolveBlock)resolve
827
+ rejecter:(RCTPromiseRejectBlock)reject {
828
+ UIViewController *presenter = [self topViewController];
829
+ if (!presenter) {
830
+ reject(@"QMS_ISSUE_DETAILS_NO_PRESENTER", @"Unable to find a view controller to present Issue Details.", nil);
831
+ return;
832
+ }
833
+
834
+ Class vcClass = NSClassFromString(@"IssueDetailViewController");
835
+ if (!vcClass) {
836
+ reject(@"QMS_ISSUE_DETAILS_UNAVAILABLE", @"QMS Issue Details screen is not available.", nil);
837
+ return;
838
+ }
839
+
840
+ UIViewController *vc = [[vcClass alloc] init];
841
+
842
+ NSString *issueIdStr = [self stringFromDictionary:options keys:@[@"issueId", @"issue_id", @"ISSUE_ID"]];
843
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
844
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
845
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
846
+
847
+ @try {
848
+ if (issueIdStr.length > 0) {
849
+ [vc setValue:@(issueIdStr.integerValue) forKey:@"issueId"];
850
+ }
851
+ if (planId.length > 0) [vc setValue:planId forKey:@"planId"];
852
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
853
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"projectCode"];
854
+ // Provide a minimal issueDetails dict so viewWillAppear doesn't crash
855
+ // before requestIssue fills in the real data.
856
+ [vc setValue:@{} forKey:@"issueDetails"];
857
+ } @catch (__unused NSException *exception) {}
858
+
859
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
860
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
861
+ @try {
862
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
863
+ vc.modalPresentationStyle = UIModalPresentationCustom;
864
+ vc.transitioningDelegate = delegate;
865
+ } @catch (__unused NSException *exception) {
866
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
867
+ }
868
+ } else {
869
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
870
+ }
871
+
872
+ [presenter presentViewController:vc animated:YES completion:^{
873
+ resolve(@YES);
874
+ }];
875
+ }
876
+
877
+ - (void)presentThirdPartyLinkWithOptions:(NSDictionary *)options
878
+ resolver:(RCTPromiseResolveBlock)resolve
879
+ rejecter:(RCTPromiseRejectBlock)reject {
880
+ NSString *lotId = [self stringFromDictionary:options keys:@[@"lotId", @"LOT_ID", @"lot_id", @"unit_lot_id"]];
881
+ if (lotId.length == 0) {
882
+ reject(@"QMS_THIRD_PARTY_MISSING_LOT_ID", @"lotId/LOT_ID/lot_id is required to open 3rd Party Link directly.", nil);
883
+ return;
884
+ }
885
+
886
+ UIViewController *presenter = [self topViewController];
887
+ if (!presenter) {
888
+ reject(@"QMS_THIRD_PARTY_NO_PRESENTER", @"Unable to find a view controller to present 3rd Party Link.", nil);
889
+ return;
890
+ }
891
+
892
+ Class vcClass = NSClassFromString(@"ThirdPartyViewController");
893
+ if (!vcClass) {
894
+ reject(@"QMS_THIRD_PARTY_UNAVAILABLE", @"QMS 3rd Party Link screen is not available.", nil);
895
+ return;
896
+ }
897
+
898
+ // Set lot_id into APIConfig so ThirdPartyViewController picks it up
899
+ [self callAPIConfigSelector:@"setLotId:" stringValue:lotId];
900
+
901
+ UIViewController *vc = [[vcClass alloc] init];
902
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
903
+ NSString *userId = [self stringFromDictionary:options keys:@[@"userId", @"USER_ID", @"user_id"]];
904
+ NSString *unitName = [self stringFromDictionary:options keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
905
+ NSString *projectName = [self stringFromDictionary:options keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
906
+ NSString *projectId = [self stringFromDictionary:options keys:@[@"projectId", @"PROJECT_ID", @"project_id"]];
907
+
908
+ @try {
909
+ [vc setValue:lotId forKey:@"lotId"];
910
+ if (planId.length > 0) [vc setValue:planId forKey:@"planId"];
911
+ if (userId.length > 0) [vc setValue:@(userId.integerValue) forKey:@"userId"];
912
+ if (unitName.length > 0) [vc setValue:unitName forKey:@"unitName"];
913
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
914
+ if (projectId.length > 0) [vc setValue:@(projectId.integerValue) forKey:@"projectId"];
915
+ } @catch (__unused NSException *exception) {}
916
+
917
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
918
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
919
+ @try {
920
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
921
+ vc.modalPresentationStyle = UIModalPresentationCustom;
922
+ vc.transitioningDelegate = delegate;
923
+ } @catch (__unused NSException *exception) {
924
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
925
+ }
926
+ } else {
927
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
928
+ }
929
+
930
+ [presenter presentViewController:vc animated:YES completion:^{
931
+ resolve(@YES);
932
+ }];
933
+ }
934
+
76
935
  - (void)routePayload:(NSDictionary *)payload {
77
936
  NSLog(@"📨 [QmsModule] routePayload called — payload class=%@", NSStringFromClass([payload class]));
78
937
  if (![payload isKindOfClass:[NSDictionary class]] || payload.count == 0) {
@@ -128,6 +987,270 @@ RCT_EXPORT_METHOD(showQmsWithPayload:(NSString *)userToken
128
987
  }];
129
988
  }
130
989
 
990
+ RCT_EXPORT_METHOD(openAddIssue:(NSString *)userToken
991
+ ClientID:(NSString *)clientID
992
+ ClientCode:(NSString *)clientCode
993
+ isOrigin:(BOOL)isOrigin
994
+ options:(NSDictionary *)options
995
+ resolver:(RCTPromiseResolveBlock)resolve
996
+ rejecter:(RCTPromiseRejectBlock)reject) {
997
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
998
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
999
+ clientID:clientID ?: @""
1000
+ clientCode:clientCode ?: @""
1001
+ isOrigin:isOrigin
1002
+ options:safeOptions];
1003
+
1004
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1005
+ clientCode:clientCode ?: @""
1006
+ isOrigin:isOrigin
1007
+ completion:^{
1008
+ [self presentAddIssueWithOptions:safeOptions resolver:resolve rejecter:reject];
1009
+ }];
1010
+ }
1011
+
1012
+ RCT_EXPORT_METHOD(openPropertyInfo:(NSString *)userToken
1013
+ ClientID:(NSString *)clientID
1014
+ ClientCode:(NSString *)clientCode
1015
+ isOrigin:(BOOL)isOrigin
1016
+ options:(NSDictionary *)options
1017
+ resolver:(RCTPromiseResolveBlock)resolve
1018
+ rejecter:(RCTPromiseRejectBlock)reject) {
1019
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1020
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1021
+ clientID:clientID ?: @""
1022
+ clientCode:clientCode ?: @""
1023
+ isOrigin:isOrigin
1024
+ options:safeOptions];
1025
+
1026
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1027
+ clientCode:clientCode ?: @""
1028
+ isOrigin:isOrigin
1029
+ completion:^{
1030
+ [self presentPropertyInfoWithOptions:safeOptions resolver:resolve rejecter:reject];
1031
+ }];
1032
+ }
1033
+
1034
+ RCT_EXPORT_METHOD(openThirdPartyLink:(NSString *)userToken
1035
+ ClientID:(NSString *)clientID
1036
+ ClientCode:(NSString *)clientCode
1037
+ isOrigin:(BOOL)isOrigin
1038
+ options:(NSDictionary *)options
1039
+ resolver:(RCTPromiseResolveBlock)resolve
1040
+ rejecter:(RCTPromiseRejectBlock)reject) {
1041
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1042
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1043
+ clientID:clientID ?: @""
1044
+ clientCode:clientCode ?: @""
1045
+ isOrigin:isOrigin
1046
+ options:safeOptions];
1047
+
1048
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1049
+ clientCode:clientCode ?: @""
1050
+ isOrigin:isOrigin
1051
+ completion:^{
1052
+ [self presentThirdPartyLinkWithOptions:safeOptions resolver:resolve rejecter:reject];
1053
+ }];
1054
+ }
1055
+
1056
+ RCT_EXPORT_METHOD(openAccessItem:(NSString *)userToken
1057
+ ClientID:(NSString *)clientID
1058
+ ClientCode:(NSString *)clientCode
1059
+ isOrigin:(BOOL)isOrigin
1060
+ options:(NSDictionary *)options
1061
+ resolver:(RCTPromiseResolveBlock)resolve
1062
+ rejecter:(RCTPromiseRejectBlock)reject) {
1063
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1064
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1065
+ clientID:clientID ?: @""
1066
+ clientCode:clientCode ?: @""
1067
+ isOrigin:isOrigin
1068
+ options:safeOptions];
1069
+
1070
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1071
+ clientCode:clientCode ?: @""
1072
+ isOrigin:isOrigin
1073
+ completion:^{
1074
+ [self presentAccessItemWithOptions:safeOptions resolver:resolve rejecter:reject];
1075
+ }];
1076
+ }
1077
+
1078
+ RCT_EXPORT_METHOD(openAppointmentList:(NSString *)userToken
1079
+ ClientID:(NSString *)clientID
1080
+ ClientCode:(NSString *)clientCode
1081
+ isOrigin:(BOOL)isOrigin
1082
+ options:(NSDictionary *)options
1083
+ resolver:(RCTPromiseResolveBlock)resolve
1084
+ rejecter:(RCTPromiseRejectBlock)reject) {
1085
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1086
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1087
+ clientID:clientID ?: @""
1088
+ clientCode:clientCode ?: @""
1089
+ isOrigin:isOrigin
1090
+ options:safeOptions];
1091
+
1092
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1093
+ clientCode:clientCode ?: @""
1094
+ isOrigin:isOrigin
1095
+ completion:^{
1096
+ [self presentAppointmentListWithOptions:safeOptions resolver:resolve rejecter:reject];
1097
+ }];
1098
+ }
1099
+
1100
+ RCT_EXPORT_METHOD(openMakeAppointment:(NSString *)userToken
1101
+ ClientID:(NSString *)clientID
1102
+ ClientCode:(NSString *)clientCode
1103
+ isOrigin:(BOOL)isOrigin
1104
+ options:(NSDictionary *)options
1105
+ resolver:(RCTPromiseResolveBlock)resolve
1106
+ rejecter:(RCTPromiseRejectBlock)reject) {
1107
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1108
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1109
+ clientID:clientID ?: @""
1110
+ clientCode:clientCode ?: @""
1111
+ isOrigin:isOrigin
1112
+ options:safeOptions];
1113
+
1114
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1115
+ clientCode:clientCode ?: @""
1116
+ isOrigin:isOrigin
1117
+ completion:^{
1118
+ [self presentMakeAppointmentWithOptions:safeOptions resolver:resolve rejecter:reject];
1119
+ }];
1120
+ }
1121
+
1122
+ RCT_EXPORT_METHOD(openPoaSignOff:(NSString *)userToken
1123
+ ClientID:(NSString *)clientID
1124
+ ClientCode:(NSString *)clientCode
1125
+ isOrigin:(BOOL)isOrigin
1126
+ options:(NSDictionary *)options
1127
+ resolver:(RCTPromiseResolveBlock)resolve
1128
+ rejecter:(RCTPromiseRejectBlock)reject) {
1129
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1130
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1131
+ clientID:clientID ?: @""
1132
+ clientCode:clientCode ?: @""
1133
+ isOrigin:isOrigin
1134
+ options:safeOptions];
1135
+
1136
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1137
+ clientCode:clientCode ?: @""
1138
+ isOrigin:isOrigin
1139
+ completion:^{
1140
+ [self presentPoaSignOffWithOptions:safeOptions resolver:resolve rejecter:reject];
1141
+ }];
1142
+ }
1143
+
1144
+ RCT_EXPORT_METHOD(openIssueList:(NSString *)userToken
1145
+ ClientID:(NSString *)clientID
1146
+ ClientCode:(NSString *)clientCode
1147
+ isOrigin:(BOOL)isOrigin
1148
+ options:(NSDictionary *)options
1149
+ resolver:(RCTPromiseResolveBlock)resolve
1150
+ rejecter:(RCTPromiseRejectBlock)reject) {
1151
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1152
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1153
+ clientID:clientID ?: @""
1154
+ clientCode:clientCode ?: @""
1155
+ isOrigin:isOrigin
1156
+ options:safeOptions];
1157
+
1158
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1159
+ clientCode:clientCode ?: @""
1160
+ isOrigin:isOrigin
1161
+ completion:^{
1162
+ [self presentIssueListWithOptions:safeOptions resolver:resolve rejecter:reject];
1163
+ }];
1164
+ }
1165
+
1166
+ RCT_EXPORT_METHOD(openProjectUpdate:(NSString *)userToken
1167
+ ClientID:(NSString *)clientID
1168
+ ClientCode:(NSString *)clientCode
1169
+ isOrigin:(BOOL)isOrigin
1170
+ options:(NSDictionary *)options
1171
+ resolver:(RCTPromiseResolveBlock)resolve
1172
+ rejecter:(RCTPromiseRejectBlock)reject) {
1173
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1174
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1175
+ clientID:clientID ?: @""
1176
+ clientCode:clientCode ?: @""
1177
+ isOrigin:isOrigin
1178
+ options:safeOptions];
1179
+
1180
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1181
+ clientCode:clientCode ?: @""
1182
+ isOrigin:isOrigin
1183
+ completion:^{
1184
+ [self presentProjectUpdateWithOptions:safeOptions resolver:resolve rejecter:reject];
1185
+ }];
1186
+ }
1187
+
1188
+ RCT_EXPORT_METHOD(openAnnouncement:(NSString *)userToken
1189
+ ClientID:(NSString *)clientID
1190
+ ClientCode:(NSString *)clientCode
1191
+ isOrigin:(BOOL)isOrigin
1192
+ options:(NSDictionary *)options
1193
+ resolver:(RCTPromiseResolveBlock)resolve
1194
+ rejecter:(RCTPromiseRejectBlock)reject) {
1195
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1196
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1197
+ clientID:clientID ?: @""
1198
+ clientCode:clientCode ?: @""
1199
+ isOrigin:isOrigin
1200
+ options:safeOptions];
1201
+
1202
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1203
+ clientCode:clientCode ?: @""
1204
+ isOrigin:isOrigin
1205
+ completion:^{
1206
+ [self presentAnnouncementWithOptions:safeOptions resolver:resolve rejecter:reject];
1207
+ }];
1208
+ }
1209
+
1210
+ RCT_EXPORT_METHOD(openAnnouncementDetails:(NSString *)userToken
1211
+ ClientID:(NSString *)clientID
1212
+ ClientCode:(NSString *)clientCode
1213
+ isOrigin:(BOOL)isOrigin
1214
+ options:(NSDictionary *)options
1215
+ resolver:(RCTPromiseResolveBlock)resolve
1216
+ rejecter:(RCTPromiseRejectBlock)reject) {
1217
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1218
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1219
+ clientID:clientID ?: @""
1220
+ clientCode:clientCode ?: @""
1221
+ isOrigin:isOrigin
1222
+ options:safeOptions];
1223
+
1224
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1225
+ clientCode:clientCode ?: @""
1226
+ isOrigin:isOrigin
1227
+ completion:^{
1228
+ [self presentAnnouncementDetailsWithOptions:safeOptions resolver:resolve rejecter:reject];
1229
+ }];
1230
+ }
1231
+
1232
+ RCT_EXPORT_METHOD(openInbox:(NSString *)userToken
1233
+ ClientID:(NSString *)clientID
1234
+ ClientCode:(NSString *)clientCode
1235
+ isOrigin:(BOOL)isOrigin
1236
+ options:(NSDictionary *)options
1237
+ resolver:(RCTPromiseResolveBlock)resolve
1238
+ rejecter:(RCTPromiseRejectBlock)reject) {
1239
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1240
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1241
+ clientID:clientID ?: @""
1242
+ clientCode:clientCode ?: @""
1243
+ isOrigin:isOrigin
1244
+ options:safeOptions];
1245
+
1246
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1247
+ clientCode:clientCode ?: @""
1248
+ isOrigin:isOrigin
1249
+ completion:^{
1250
+ [self presentInboxWithOptions:safeOptions resolver:resolve rejecter:reject];
1251
+ }];
1252
+ }
1253
+
131
1254
  RCT_EXPORT_METHOD(setPrimaryColor:(NSString *)hexColor) {
132
1255
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
133
1256
  if (hexColor.length > 0) [defaults setObject:hexColor forKey:kQmsThemePrimaryColorKey];
@@ -259,4 +1382,174 @@ RCT_EXPORT_METHOD(emitInAppNotificationWithPayload:(NSString *)title
259
1382
  if (self.bannerView == gesture.view) self.bannerView = nil;
260
1383
  }
261
1384
 
1385
+ // ---------------------------------------------------------------------------
1386
+ // Draft Issues
1387
+ // ---------------------------------------------------------------------------
1388
+
1389
+ RCT_EXPORT_METHOD(openAppointmentDetails:(NSString *)userToken
1390
+ ClientID:(NSString *)clientID
1391
+ ClientCode:(NSString *)clientCode
1392
+ isOrigin:(BOOL)isOrigin
1393
+ options:(NSDictionary *)options
1394
+ resolver:(RCTPromiseResolveBlock)resolve
1395
+ rejecter:(RCTPromiseRejectBlock)reject) {
1396
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1397
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1398
+ clientID:clientID ?: @""
1399
+ clientCode:clientCode ?: @""
1400
+ isOrigin:isOrigin
1401
+ options:safeOptions];
1402
+
1403
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1404
+ clientCode:clientCode ?: @""
1405
+ isOrigin:isOrigin
1406
+ completion:^{
1407
+ [self presentAppointmentDetailsWithOptions:safeOptions resolver:resolve rejecter:reject];
1408
+ }];
1409
+ }
1410
+
1411
+ RCT_EXPORT_METHOD(openIssueDetails:(NSString *)userToken
1412
+ ClientID:(NSString *)clientID
1413
+ ClientCode:(NSString *)clientCode
1414
+ isOrigin:(BOOL)isOrigin
1415
+ options:(NSDictionary *)options
1416
+ resolver:(RCTPromiseResolveBlock)resolve
1417
+ rejecter:(RCTPromiseRejectBlock)reject) {
1418
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1419
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1420
+ clientID:clientID ?: @""
1421
+ clientCode:clientCode ?: @""
1422
+ isOrigin:isOrigin
1423
+ options:safeOptions];
1424
+
1425
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1426
+ clientCode:clientCode ?: @""
1427
+ isOrigin:isOrigin
1428
+ completion:^{
1429
+ [self presentIssueDetailsWithOptions:safeOptions resolver:resolve rejecter:reject];
1430
+ }];
1431
+ }
1432
+
1433
+ /// Returns the number of draft (offline) issues for the given projectCode.
1434
+ /// Reads OFFLINE_SYNC_QUEUE from LocalStorage and counts ADD_ISSUE entries
1435
+ /// whose unitName matches the provided projectCode (unit name in QMS terms).
1436
+ RCT_EXPORT_METHOD(getDraftCount:(NSString *)projectCode
1437
+ resolver:(RCTPromiseResolveBlock)resolve
1438
+ rejecter:(RCTPromiseRejectBlock)reject) {
1439
+ Class localStorageClass = NSClassFromString(@"LocalStorage");
1440
+ if (!localStorageClass) {
1441
+ resolve(@(0));
1442
+ return;
1443
+ }
1444
+
1445
+ SEL loadSel = NSSelectorFromString(@"loadArrayForKey:");
1446
+ if (![localStorageClass respondsToSelector:loadSel]) {
1447
+ resolve(@(0));
1448
+ return;
1449
+ }
1450
+
1451
+ NSArray *queue = [localStorageClass performSelector:loadSel withObject:@"OFFLINE_SYNC_QUEUE"];
1452
+ if (![queue isKindOfClass:[NSArray class]] || queue.count == 0) {
1453
+ resolve(@(0));
1454
+ return;
1455
+ }
1456
+
1457
+ NSString *safeCode = (projectCode.length > 0) ? projectCode : @"";
1458
+ NSInteger count = 0;
1459
+ for (id obj in queue) {
1460
+ if (![obj isKindOfClass:[NSDictionary class]]) continue;
1461
+ NSString *type = [obj[@"type"] description] ?: @"";
1462
+ if (![type isEqualToString:@"ADD_ISSUE"]) continue;
1463
+ NSString *unitName = [obj[@"unitName"] description] ?: @"";
1464
+ if (safeCode.length == 0 || [unitName isEqualToString:safeCode]) {
1465
+ count++;
1466
+ }
1467
+ }
1468
+ resolve(@(count));
1469
+ }
1470
+
1471
+ /// Opens the DraftIssuesViewController for the current unit's draft issues.
1472
+ RCT_EXPORT_METHOD(openDraftIssues:(NSString *)userToken
1473
+ ClientID:(NSString *)clientID
1474
+ ClientCode:(NSString *)clientCode
1475
+ isOrigin:(BOOL)isOrigin
1476
+ options:(NSDictionary *)options
1477
+ resolver:(RCTPromiseResolveBlock)resolve
1478
+ rejecter:(RCTPromiseRejectBlock)reject) {
1479
+ NSDictionary *safeOptions = [options isKindOfClass:[NSDictionary class]] ? options : @{};
1480
+ [self configureAPIForAddIssueWithToken:userToken ?: @""
1481
+ clientID:clientID ?: @""
1482
+ clientCode:clientCode ?: @""
1483
+ isOrigin:isOrigin
1484
+ options:safeOptions];
1485
+
1486
+ [self fetchCompanyUrlForAddIssueWithToken:userToken ?: @""
1487
+ clientCode:clientCode ?: @""
1488
+ isOrigin:isOrigin
1489
+ completion:^{
1490
+ UIViewController *presenter = [self topViewController];
1491
+ if (!presenter) {
1492
+ reject(@"QMS_DRAFT_NO_PRESENTER", @"Unable to find a view controller to present Draft Issues.", nil);
1493
+ return;
1494
+ }
1495
+
1496
+ Class vcClass = NSClassFromString(@"DraftIssuesViewController");
1497
+ if (!vcClass) {
1498
+ reject(@"QMS_DRAFT_UNAVAILABLE", @"DraftIssuesViewController is not available.", nil);
1499
+ return;
1500
+ }
1501
+
1502
+ UIViewController *vc = [[vcClass alloc] init];
1503
+
1504
+ NSString *projectName = [self stringFromDictionary:safeOptions
1505
+ keys:@[@"projectName", @"PROJECT_NAME", @"project_name"]];
1506
+ NSString *projectCode = [self stringFromDictionary:safeOptions
1507
+ keys:@[@"unitName", @"UNIT_NAME", @"unit_name"]];
1508
+ NSString *issueLimitStr = [self stringFromDictionary:safeOptions
1509
+ keys:@[@"issueLimit", @"ISSUE_LIMIT", @"issue_limit"]];
1510
+ NSString *issueLimitWarning = [self stringFromDictionary:safeOptions
1511
+ keys:@[@"issueLimitWarningText",
1512
+ @"ISSUE_LIMIT_WARNING_TEXT",
1513
+ @"issue_limit_warning_text"]];
1514
+ NSString *projectId = [self stringFromDictionary:options keys:@[@"projectId", @"PROJECT_ID", @"project_id"]];
1515
+ NSString *planId = [self stringFromDictionary:options keys:@[@"drawingPlanId", @"DRAWING_PLAN_ID", @"drawing_plan_id", @"plan_id", @"unitId", @"unit_id"]];
1516
+
1517
+ @try {
1518
+ if (projectName.length > 0) [vc setValue:projectName forKey:@"projectName"];
1519
+ if (projectCode.length > 0) [vc setValue:projectCode forKey:@"projectCode"];
1520
+ if (issueLimitStr.length > 0) [vc setValue:@(issueLimitStr.integerValue) forKey:@"issueLimit"];
1521
+ if (issueLimitWarning.length > 0) [vc setValue:issueLimitWarning forKey:@"issueLimitWarning"];
1522
+
1523
+ // multiZone flag
1524
+ id multiZoneRaw = safeOptions[@"multiZone"] ?: safeOptions[@"MULTI_ZONE"] ?: safeOptions[@"multi_zone"];
1525
+ NSLog(@"🔍 [openDraftIssues] multiZone raw value: %@ (class: %@)", multiZoneRaw, NSStringFromClass([multiZoneRaw class]));
1526
+ BOOL multiZoneBln = [multiZoneRaw isKindOfClass:[NSNumber class]] ? [multiZoneRaw boolValue] :
1527
+ [multiZoneRaw isKindOfClass:[NSString class]] ? [multiZoneRaw isEqualToString:@"1"] : NO;
1528
+ NSLog(@"🔍 [openDraftIssues] multiZone resolved BOOL: %@", multiZoneBln ? @"YES" : @"NO");
1529
+ [self callAPIConfigSelector:@"setIsMulti:" boolValue:multiZoneBln];
1530
+ NSLog(@"🔍 [openDraftIssues] APIConfig.isMulti after set: %@", [NSClassFromString(@"APIConfig") valueForKey:@"isMulti"] ? @"YES" : @"NO");
1531
+ [vc setValue:@(multiZoneBln) forKey:@"isMulti"];
1532
+ } @catch (__unused NSException *exception) {
1533
+ NSLog(@"⚠️ [openDraftIssues] exception while setting multiZone: %@", exception);
1534
+ }
1535
+
1536
+ Class delegateClass = NSClassFromString(@"QmsSlideModalTransitionDelegate");
1537
+ if (delegateClass && [delegateClass respondsToSelector:NSSelectorFromString(@"shared")]) {
1538
+ @try {
1539
+ id delegate = [delegateClass performSelector:NSSelectorFromString(@"shared")];
1540
+ vc.modalPresentationStyle = UIModalPresentationCustom;
1541
+ vc.transitioningDelegate = delegate;
1542
+ } @catch (__unused NSException *exception) {
1543
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
1544
+ }
1545
+ } else {
1546
+ vc.modalPresentationStyle = UIModalPresentationFullScreen;
1547
+ }
1548
+
1549
+ [presenter presentViewController:vc animated:YES completion:^{
1550
+ resolve(@YES);
1551
+ }];
1552
+ }];
1553
+ }
1554
+
262
1555
  @end