diff --git a/assets/images/attachment_icon.svg b/assets/images/attachment_icon.svg new file mode 100644 index 00000000..d0da9eaf --- /dev/null +++ b/assets/images/attachment_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/collesped_arrow_icon.svg b/assets/images/collesped_arrow_icon.svg new file mode 100644 index 00000000..7980fef7 --- /dev/null +++ b/assets/images/collesped_arrow_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/expended_arrow_icon.svg b/assets/images/expended_arrow_icon.svg new file mode 100644 index 00000000..c1085125 --- /dev/null +++ b/assets/images/expended_arrow_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index cfead080..2e7e5f56 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -270,7 +270,9 @@ class URLs { //Traf static get addTRAF => "$_baseUrl/TRAF/AddTRAF"; // get static get getTRAFById => "$_baseUrl/TRAF/GetTRAFById"; // get + static get getAssetAnalytics => "$_baseUrl/Asset/GetAssetAnalytics"; // get static get getAssetNDAutoCompleteByDynamicCodes => "$_baseUrl/AssetNameDefinition/GetAssetNDAutoCompleteByDynamicCodes"; // get// get + static get getAssetNameAutoComplete => "$_baseUrl/AssetNameDefinition/GetAssetNameAutoComplete"; // get// get //gas refill static get getGasTypes => "$_baseUrl/Lookups/GetLookup?lookupEnum=606"; // get @@ -307,11 +309,13 @@ class URLs { static get getNurses => "$_baseUrl/Account/GetUserByRoleValue?value=R-7"; // get static get getNursesBySiteId => "$_baseUrl/Account/GetUserByRoleValueSiteAndAssetGroupBySiteId?value=R-7"; // get static get getUsersBasedOnSearch => "$_baseUrl/Account/GetUsersBasedOnSearch"; // get + static get getUserByRoleValueSiteMobileAutocomplete => "$_baseUrl/Account/GetUserByRoleValueSiteMobileAutocomplete"; // get // pentry static get getPentry => "$_baseUrl/return/pentry/details"; // get static get updatePentry => "$_baseUrl/Visit/UpdateVisit"; // get static get getPentryTaskStatus => "$_baseUrl/Lookups/GetLookup?lookupEnum=403"; // get + // get static get getPentryVisitStatus => "$_baseUrl/Lookups/GetLookup?lookupEnum=402"; // get static get getPentryStatus => "$_baseUrl/Lookups/GetLookup?lookupEnum=401"; // get // contacts @@ -347,11 +351,22 @@ class URLs { static get getClassification => '$_baseUrl/AssetInventory/Classification'; + //internal Audit + static get getInternalAuditEquipmentById => "$_baseUrl/InternalAuditEquipments/GetInternalAuditEquipmentById"; + static get getInternalAuditSystemById => "$_baseUrl/InternalAuditSystems/GetInternalAuditSystemByIdV2"; + static get getInternalAuditChecklist => "$_baseUrl/Lookups/GetLookup?lookupEnum=43"; + static get getInternalAuditWoType => "$_baseUrl/Lookups/GetLookup?lookupEnum=2500"; + static get getInternalAuditFindingType => "$_baseUrl/Lookups/GetLookup?lookupEnum=2502"; + static get addOrUpdateEquipmentInternalAudit => "$_baseUrl/InternalAuditEquipments/AddOrUpdateAuditEquipment"; + static get addOrUpdateInternalAuditSystem => "$_baseUrl/InternalAuditSystems/AddOrUpdateInternalAuditSystem"; + static get getWoAutoComplete => "$_baseUrl/InternalAuditSystems/AutoCompleteAllWorkOrder"; + static get updateAuditEquipmentsEngineer => "$_baseUrl/InternalAuditEquipments/UpdateAuditEquipmentsEngineer"; + static get updateAuditSystemEngineer => "$_baseUrl/InternalAuditSystems/UpdateAuditSystemEngineer"; + static get loadAllWorkOrderDetailsByID => "$_baseUrl/InternalAuditSystems/LoadAllWorkOrderDetailsByID"; + //chat static get chatSdkToken => '$chatHubUrlApi/auth/sdk-token'; - //survey static get getQuestionnaire => '$_baseUrl/SurveyQuestionnaire/GetQuestionnaire'; - static get submitSurvey => '$_baseUrl/SurveyQuestionnaire/SubmitSurvey'; } diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index 470f803d..99f77a02 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -133,15 +133,24 @@ class AllRequestsProvider extends ChangeNotifier { List getStatues(BuildContext context) { List list = [1, 2, 3, 4]; + //TODO need to refactor this code .... if (context.userProvider.isAssessor) { list = [9]; return list; } + if (context.userProvider.isQualityUser) { + + list = [10,11]; + return list; + } if (!context.userProvider.isNurse) { list.add(5); } + if (context.userProvider.isEngineer) { + list.add(10); + } list.add(6); // task module if (context.settingProvider.isUserFlowMedical && !context.userProvider.isNurse) { diff --git a/lib/controllers/providers/api/oracle_code_provider.dart b/lib/controllers/providers/api/oracle_code_provider.dart index ea8b0978..71375675 100644 --- a/lib/controllers/providers/api/oracle_code_provider.dart +++ b/lib/controllers/providers/api/oracle_code_provider.dart @@ -88,7 +88,7 @@ class OracleCodeProvider extends ChangeNotifier { Future> getAssetByOracleCode(String oracleCode) async { late Response response; try { - response = await ApiManager.instance.post(URLs.getAssetNDAutoCompleteByDynamicCodes, body: {"codeValue": oracleCode}); + response = await ApiManager.instance.post(URLs.getAssetNameAutoComplete, body: {"pageSize": 1, "pageNumber": 20, "searchText": oracleCode}); List page = []; if (response.statusCode >= 200 && response.statusCode < 300) { // client's request was successfully received diff --git a/lib/controllers/providers/api/search_user_provider.dart b/lib/controllers/providers/api/search_user_provider.dart index cdaecdb2..f13a30ca 100644 --- a/lib/controllers/providers/api/search_user_provider.dart +++ b/lib/controllers/providers/api/search_user_provider.dart @@ -46,10 +46,10 @@ class UserSearchProvider extends ChangeNotifier { notifyListeners(); } - Future> getUsersBasedOnSearch(String query) async { + Future> getUsersBasedOnSearch(int siteId, String query) async { late Response response; try { - response = await ApiManager.instance.get(URLs.getUsersBasedOnSearch + "?roleValue=R-7&searchText=$query"); + response = await ApiManager.instance.get(URLs.getUserByRoleValueSiteMobileAutocomplete + "?siteId=$siteId&roleValue=R-7&searchText=$query"); List page = []; if (response.statusCode >= 200 && response.statusCode < 300) { // client's request was successfully received diff --git a/lib/controllers/providers/api/user_provider.dart b/lib/controllers/providers/api/user_provider.dart index 481d06d5..471be087 100644 --- a/lib/controllers/providers/api/user_provider.dart +++ b/lib/controllers/providers/api/user_provider.dart @@ -45,6 +45,7 @@ class UserProvider extends ChangeNotifier { bool get isNurse => user!.type == UsersTypes.normal_user; bool get isAssessor => user!.type == UsersTypes.assessor || user!.type == UsersTypes.assessorTl; + bool get isQualityUser => user!.type == UsersTypes.qualityUser; VerifyOtpModel _verifyOtpModel = VerifyOtpModel(); SwipeTransaction _swipeTransactionModel = SwipeTransaction(); diff --git a/lib/dashboard_latest/widgets/request_category_list.dart b/lib/dashboard_latest/widgets/request_category_list.dart index 07d5d92d..9ca9daff 100644 --- a/lib/dashboard_latest/widgets/request_category_list.dart +++ b/lib/dashboard_latest/widgets/request_category_list.dart @@ -3,6 +3,7 @@ import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/new_models/dashboard_detail.dart'; import 'package:test_sa/modules/asset_inventory_module/pages/inventory_session_item_view.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart'; import 'package:test_sa/modules/tm_module/tasks_wo/task_request_item_view.dart'; import 'package:test_sa/modules/traf_module/traf_request_item_view.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; @@ -56,6 +57,11 @@ class RequestCategoryList extends StatelessWidget { return InventorySessionItemView(requestData: request); case 9: return TrafRequestItemView(requestData: request); + //TODO need to verify this ... + case 10: + return EquipmentInternalAuditItemView(requestData: request); + case 11: + return EquipmentInternalAuditItemView(requestData: request); default: return Container( height: 100, diff --git a/lib/main.dart b/lib/main.dart index 6b47386e..8855155a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,6 +31,12 @@ import 'package:test_sa/modules/asset_inventory_module/provider/asset_inventory_ import 'package:test_sa/modules/cm_module/service_request_detail_provider.dart'; import 'package:test_sa/modules/cm_module/views/nurse/create_new_request_view.dart'; import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart'; import 'package:test_sa/modules/tm_module/tasks_wo/create_task_view.dart'; import 'package:test_sa/modules/traf_module/create_traf_request_page.dart'; import 'package:test_sa/modules/traf_module/traf_request_provider.dart'; @@ -100,6 +106,8 @@ import 'controllers/providers/api/gas_refill_comments.dart'; import 'controllers/providers/api/user_provider.dart'; import 'controllers/providers/settings/setting_provider.dart'; import 'dashboard_latest/dashboard_provider.dart'; +import 'modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart'; +import 'modules/internal_audit_module/provider/internal_audit_checklist_provider.dart'; import 'modules/cx_module/survey/survey_provider.dart'; import 'new_views/pages/gas_refill_request_form.dart'; import 'providers/lookups/classification_lookup_provider.dart'; @@ -291,6 +299,10 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => PpmServiceProvider()), ChangeNotifierProvider(create: (_) => CommentsProvider()), ChangeNotifierProvider(create: (_) => GasRefillCommentsProvider()), + ChangeNotifierProvider(create: (_) => InternalAuditCheckListProvider()), + ChangeNotifierProvider(create: (_) => InternalAuditWoTypeProvider()), + ChangeNotifierProvider(create: (_) => InternalAuditProvider()), + ChangeNotifierProvider(create: (_) => InternalAuditFindingTypeProvider()), ///todo deleted //ChangeNotifierProvider(create: (_) => RequestStatusProvider()), @@ -359,6 +371,10 @@ class MyApp extends StatelessWidget { ProfilePage.id: (_) => const ProfilePage(), ReportBugPage.id: (_) => const ReportBugPage(), HelpCenterPage.id: (_) => const HelpCenterPage(), + CreateEquipmentInternalAuditForm.id: (_) => const CreateEquipmentInternalAuditForm(), + CreateSystemInternalAuditForm.id: (_) => const CreateSystemInternalAuditForm(), + UpdateEquipmentInternalAuditPage.id: (_) => UpdateEquipmentInternalAuditPage(), + UpdateSystemInternalAuditPage.id: (_) => UpdateSystemInternalAuditPage(), // SwipeSuccessView.routeName: (_) => const SwipeSuccessView(), // SwipeHistoryView.routeName: (_) => const SwipeHistoryView(), }, diff --git a/lib/models/enums/user_types.dart b/lib/models/enums/user_types.dart index 63f1d9d7..cd6b5f13 100644 --- a/lib/models/enums/user_types.dart +++ b/lib/models/enums/user_types.dart @@ -3,5 +3,6 @@ enum UsersTypes { normal_user, // 1 nurse, // 1 assessor, - assessorTl + assessorTl, + qualityUser } diff --git a/lib/models/helper_data_models/maintenance_request/activity_maintenance_model.dart b/lib/models/helper_data_models/maintenance_request/activity_maintenance_model.dart index dda5432b..21541769 100644 --- a/lib/models/helper_data_models/maintenance_request/activity_maintenance_model.dart +++ b/lib/models/helper_data_models/maintenance_request/activity_maintenance_model.dart @@ -31,11 +31,11 @@ class ActivityMaintenanceHelperModel { WorkOrderAssignedEmployee? assignedEmployee; SuppEngineerWorkOrders? supEngineer; ActivityMaintenanceAssistantEmployees? modelAssistantEmployees; + List? assistantEmployList=[]; List? assistantEmployees; List? activityMaintenanceTimers = []; TimerModel? activityMaintenanceTimerModel = TimerModel(); TimerModel? activityTimePicker; - List? assistantEmployList=[]; List? timerModelList = []; ActivityMaintenanceHelperModel( diff --git a/lib/models/new_models/users_based_on_search_model.dart b/lib/models/new_models/users_based_on_search_model.dart index 54480f91..4dea3117 100644 --- a/lib/models/new_models/users_based_on_search_model.dart +++ b/lib/models/new_models/users_based_on_search_model.dart @@ -6,17 +6,11 @@ class UsersBasedOnSearchModel { int? languageId; String? extensionNo; String? phoneNumber; + String? positionName; + String? position; bool? isActive; - UsersBasedOnSearchModel( - {this.userId, - this.userName, - this.email, - this.employeeId, - this.languageId, - this.extensionNo, - this.phoneNumber, - this.isActive}); + UsersBasedOnSearchModel({this.userId, this.userName, this.email, this.employeeId, this.languageId, this.extensionNo, this.phoneNumber, this.positionName, this.position, this.isActive}); UsersBasedOnSearchModel.fromJson(Map json) { userId = json['userId']; @@ -26,6 +20,8 @@ class UsersBasedOnSearchModel { languageId = json['languageId']; extensionNo = json['extensionNo']; phoneNumber = json['phoneNumber']; + positionName = json['positionName']; + position = json['position']; isActive = json['isActive']; } @@ -38,6 +34,8 @@ class UsersBasedOnSearchModel { data['languageId'] = this.languageId; data['extensionNo'] = this.extensionNo; data['phoneNumber'] = this.phoneNumber; + data['positionName'] = this.positionName; + data['position'] = this.position; data['isActive'] = this.isActive; return data; } diff --git a/lib/models/user.dart b/lib/models/user.dart index 20158748..2fd42598 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -1,3 +1,4 @@ +import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -107,6 +108,8 @@ class User { return UsersTypes.assessor; case "R-19": // Head Nurse Role return UsersTypes.assessorTl; + case "R-3": // Quality User + return UsersTypes.qualityUser; default: return null; } diff --git a/lib/modules/asset_inventory_module/models/asset_inventory_model.dart b/lib/modules/asset_inventory_module/models/asset_inventory_model.dart index 0b6796cb..4a4e6587 100644 --- a/lib/modules/asset_inventory_module/models/asset_inventory_model.dart +++ b/lib/modules/asset_inventory_module/models/asset_inventory_model.dart @@ -100,13 +100,13 @@ class AssetInventoryModel { String? photo; String? photoOriginName; String? remarks; - Site? site; Building? building; Floor? floor; Department? department; Rooms? room; SupplierDetails? supplier; + List assetInventoryDetailAssetAttachments = []; AssetInventoryModel({ this.id, @@ -160,6 +160,7 @@ class AssetInventoryModel { this.floor, this.room, this.supplier, + this.assetInventoryDetailAssetAttachments = const [], }); AssetInventoryModel.fromJson(Map json) { @@ -194,6 +195,13 @@ class AssetInventoryModel { photo = json['photo']; photoOriginName = json['photoOriginName']; remarks = json['remarks']; + if (json['assetTransferAttachments'] != null) { + assetInventoryDetailAssetAttachments = []; + json['assetInventoryDetailAssetAttachments'].forEach((v) { + assetInventoryDetailAssetAttachments.add(AssetInventoryDetailAssetAttachments.fromJson(v)); + }); + } + //new data.. // newAssetNumber = json['newAssetNumber'] ?? json['assetNumber']; // manufacturerId = json['manufacturerId'] ?? json['manufacturerId']; @@ -232,6 +240,9 @@ class AssetInventoryModel { 'newSupplierName': newSupplierName, 'photo': photo, 'remarks': remarks, + 'assetInventoryDetailAssetAttachments': assetInventoryDetailAssetAttachments != null + ? assetInventoryDetailAssetAttachments!.map((v) => v.toJson()).toList() + : [], }; } @@ -290,8 +301,45 @@ class AssetInventoryModel { department: department ?? other.department, room: room ?? other.room, supplier: supplier ?? other.supplier, + assetInventoryDetailAssetAttachments: assetInventoryDetailAssetAttachments??other.assetInventoryDetailAssetAttachments, ); } } +class AssetInventoryDetailAssetAttachments { + AssetInventoryDetailAssetAttachments({ + this.id, + this.name, + this.originalName, + }); + + AssetInventoryDetailAssetAttachments.fromJson(dynamic json) { + id = json['id']; + name = json['name']; + originalName = json['originalName']; + } + + num? id; + String? name; + String? originalName; + + AssetInventoryDetailAssetAttachments copyWith({ + num? id, // Parameter is now nullable + String? name, // Parameter is now nullable + String? originalName, // Parameter is now nullable + }) => + AssetInventoryDetailAssetAttachments( + id: id ?? this.id, + name: name??this.name, + originalName: originalName??this.originalName, + ); + + Map toJson() { + final map = {}; + map['id'] = id; + map['name'] = name; + map['originalName'] = originalName; + return map; + } +} diff --git a/lib/modules/asset_inventory_module/pages/asset_detail_card_view.dart b/lib/modules/asset_inventory_module/pages/asset_detail_card_view.dart index c49a836f..9d128718 100644 --- a/lib/modules/asset_inventory_module/pages/asset_detail_card_view.dart +++ b/lib/modules/asset_inventory_module/pages/asset_detail_card_view.dart @@ -24,6 +24,7 @@ class AssetDetailCardView extends StatelessWidget { @override Widget build(BuildContext context) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ @@ -37,81 +38,75 @@ class AssetDetailCardView extends StatelessWidget { ], ), 8.height, - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'transaction Date: ${assetInventoryModel.transactionDate?.toServiceRequestDetailsFormat ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.assetNumber}: ${assetInventoryModel.assetNumber ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.serialNo}: ${assetInventoryModel.serialNo ?? '-'}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.manufacture}: ${assetInventoryModel.manufacturer ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.model}: ${assetInventoryModel.model ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.site}: ${assetInventoryModel.siteName ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.building}: ${assetInventoryModel.buildingName ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.floor}: ${assetInventoryModel.floorName ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.department}: ${assetInventoryModel.departmentName ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.supplier}: ${assetInventoryModel.supplierName ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${'Classifications'.addTranslation}: ${assetInventoryModel.status ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${'Remarks'.addTranslation}: ${assetInventoryModel.remarks ?? ''}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - ], - ).expanded, - Container( - decoration: BoxDecoration(color: AppColor.neutral100, borderRadius: BorderRadius.circular(15)), - height: 115.toScreenHeight, - width: 115.toScreenWidth, - margin: const EdgeInsets.only(top: 8), - child: assetInventoryModel.photo != null && assetInventoryModel.photo!.isNotEmpty - ? ClipRRect( - borderRadius: BorderRadius.circular(8), - child: ImageLoader( - url: URLs.getFileUrl(assetInventoryModel.photo), - boxFit: BoxFit.cover, - height: 48, - width: 48, - )) - : 'image_placeholder'.toSvgAsset().center, - ) - ], + Text( + 'Inventory Date: ${assetInventoryModel.transactionDate?.toServiceRequestDetailsFormat ?? '-'}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.assetNumber}: ${assetInventoryModel.assetNumber ?? '-'}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), Text( + '${context.translation.assetName}: ${assetInventoryModel.assetName ?? '-'}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.serialNo}: ${assetInventoryModel.serialNo ?? '-'}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.manufacture}: ${assetInventoryModel.manufacturer ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.model}: ${assetInventoryModel.model ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.site}: ${assetInventoryModel.siteName ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.building}: ${assetInventoryModel.buildingName ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.floor}: ${assetInventoryModel.floorName ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.department}: ${assetInventoryModel.departmentName ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.supplier}: ${assetInventoryModel.supplierName ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${'Classifications'.addTranslation}: ${assetInventoryModel.status ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${'Remarks'.addTranslation}: ${assetInventoryModel.remarks ?? ''}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), ], ).toShadowContainer(context, borderRadius: 20, padding: 12); } } +//TODO old code. +// Container( +// decoration: BoxDecoration(color: AppColor.neutral100, borderRadius: BorderRadius.circular(15)), +// height: 115.toScreenHeight, +// width: 115.toScreenWidth, +// margin: const EdgeInsets.only(top: 8), +// child: assetInventoryModel.photo != null && assetInventoryModel.photo!.isNotEmpty +// ? ClipRRect( +// borderRadius: BorderRadius.circular(8), +// child: ImageLoader( +// url: URLs.getFileUrl(assetInventoryModel.photo), +// boxFit: BoxFit.cover, +// height: 48, +// width: 48, +// )) +// : 'image_placeholder'.toSvgAsset().center, +// ) diff --git a/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart b/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart index 5c826f56..9337013a 100644 --- a/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart +++ b/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart @@ -4,6 +4,7 @@ import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/new_models/site.dart'; import 'package:test_sa/modules/asset_inventory_module/models/session_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; @@ -48,12 +49,15 @@ class _AssetInventoryDetailViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + // StatusLabel( label: sessionModel.statusName, id: sessionModel.statusId, radius: 4, - textColor: AppColor.green15, - backgroundColor: AppColor.greenStatus(context), + textColor: AppColor.getRequestStatusTextColorByName(context, sessionModel.statusName), + backgroundColor: AppColor.getRequestStatusColorByName(context, sessionModel.statusName), + // textColor: AppColor.green15, + // backgroundColor: AppColor.greenStatus(context), ), Text( sessionModel.createdDate!.toString().toServiceRequestCardFormat, @@ -64,7 +68,7 @@ class _AssetInventoryDetailViewState extends State { ), 8.height, Text( - context.translation.requestDetails, + 'Session Details'.addTranslation, style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), 8.height, @@ -100,64 +104,139 @@ class _AssetInventoryDetailViewState extends State { style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), 8.height, - ListView.separated( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - padding: EdgeInsets.zero, - itemCount: sites.length, - itemBuilder: (cxt, siteIndex) { - final site = sites[siteIndex]; - final buildingNames = (site.buildings ?? []).map((b) => b.name).where((name) => name != null && name!.trim().isNotEmpty).join(', '); - final floorNames = (site.buildings ?? []).expand((b) => b.floors ?? []).map((f) => f.name).where((name) => name != null && name!.trim().isNotEmpty).join(', '); - final departmentNames = - (site.buildings ?? []).expand((b) => b.floors ?? []).expand((f) => f.departments ?? []).map((d) => d.name).where((name) => name != null && name!.trim().isNotEmpty).join(', '); - final roomNames = (site.buildings ?? []) - .expand((b) => b.floors ?? []) - .expand((f) => f.departments ?? []) - .expand((d) => d.rooms ?? []) - .map((r) => r.name) - .where((name) => name != null && name!.trim().isNotEmpty) - .join(', '); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + SiteHierarchyView( + sites: sites, + ), + ], + ).toShadowContainer(context, padding: 12, borderRadius: 20); + } +} + + + +class SiteHierarchyView extends StatelessWidget { + final List sites; + + const SiteHierarchyView({super.key, required this.sites}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: sites.length, + itemBuilder: (context, index) { + final site = sites[index]; + return HierarchyTile( + title: site.name ?? '-', + children: (site.buildings ?? []) + .map((b) => HierarchyTile( + title: b.name ?? '-', + children: (b.floors ?? []) + .map((f) => HierarchyTile( + title: f.name ?? '-', + children: (f.departments ?? []) + .map((d) => HierarchyTile( + title: d.name ?? '-', + children: (d.rooms ?? []) + .map((r) => LeafTile(title: r.name ?? '-')) + .toList(), + )) + .toList(), + )) + .toList(), + )) + .toList(), + ); + }, + ); + } +} +class HierarchyTile extends StatefulWidget { + final String title; + final List children; + final double indent; + + const HierarchyTile({ + super.key, + required this.title, + this.children = const [], + this.indent = 0, + }); + + @override + State createState() => _HierarchyTileState(); +} + +class _HierarchyTileState extends State { + bool expanded = false; + + @override + Widget build(BuildContext context) { + final hasChildren = widget.children.isNotEmpty; + return Padding( + padding: EdgeInsets.only(left: widget.indent,top: 10.toScreenHeight), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: hasChildren ? () => setState(() => expanded = !expanded) : null, + child: Row( children: [ + if (hasChildren) + // expanded? 'expended_arrow_icon'.toSvgAsset(color: AppColor.neutral50,width: 8,height:13): 'collesped_arrow_icon'.toSvgAsset(color: AppColor.neutral50,width: 8,height:13) + Icon( + expanded ? Icons.expand_more : Icons.chevron_right, + size: 25.toScreenHeight, + color: AppColor.neutral50, + ) + else + SizedBox(width: 18.toScreenWidth), + SizedBox(width: 15.toScreenWidth), Text( - site.name ?? '-', - style: AppTextStyles.heading6.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, - ), - ), - Text( - '${context.translation.building}: ${buildingNames.isNotEmpty ? buildingNames : '-'}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - Text( - '${context.translation.floor}: ${floorNames.isNotEmpty ? floorNames : '-'}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - Text( - '${context.translation.department}: ${departmentNames.isNotEmpty ? departmentNames : '-'}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), - ), - Text( - '${context.translation.room}: ${roomNames.isNotEmpty ? roomNames : '-'}', - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, - ), + widget.title, + style:AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), ), ], - ); - }, - separatorBuilder: (cxt, index) => const Divider().defaultStyle(context), - ), - ], - ).toShadowContainer(context, padding: 12, borderRadius: 20); + ), + ), + if (expanded) + Padding( + padding: const EdgeInsets.only(left: 20, top: 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widget.children + .map((child) => child is HierarchyTile + ? HierarchyTile( + title: (child).title, + indent: 0, + children: (child).children, + ) + : child) + .toList(), + ), + ), + ], + ), + ); + } +} +class LeafTile extends StatelessWidget { + final String title; + + const LeafTile({super.key, required this.title}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 22, top: 4, bottom: 4), + child: Text( + title, + style: const TextStyle(fontSize: 13, color: Colors.black87), + ), + ); } } + + diff --git a/lib/modules/asset_inventory_module/pages/asset_inventory_form_view.dart b/lib/modules/asset_inventory_module/pages/asset_inventory_form_view.dart index fd4bea7c..f6c2e0b2 100644 --- a/lib/modules/asset_inventory_module/pages/asset_inventory_form_view.dart +++ b/lib/modules/asset_inventory_module/pages/asset_inventory_form_view.dart @@ -294,30 +294,17 @@ class _AssetInventoryFormViewState extends State { ], 12.height, Text( - 'Asset Photo'.addTranslation, + 'Attachments'.addTranslation, style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.white936), ), 12.height, - attachments.isEmpty - ? AttachmentPicker( - label: context.translation.attachImage, - attachment: attachments, - buttonColor: AppColor.black10, - onlyImages: true, - onChange: (value) { - setState(() {}); - }, - buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.neutral120), - ) - : MultiFilesPickerItem( - file: File(attachments.first.name ?? ''), - enabled: true, - onRemoveTap: (file) { - setState(() { - attachments.clear(); - }); - }, - ), + AttachmentPicker( + label: context.translation.attachFiles, + attachment: attachments, + buttonColor: AppColor.black10, + onlyImages: false, + buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.neutral120), + ), 12.height, AppTextFormField( backgroundColor: AppColor.fieldBgColor(context), @@ -338,7 +325,7 @@ class _AssetInventoryFormViewState extends State { .expanded, FooterActionButton.footerContainer( context: context, - child: AppFilledButton(buttonColor: AppColor.primary10, label: context.translation.submitRequest, maxWidth: true, onPressed: _onSubmit), + child: AppFilledButton(buttonColor: AppColor.primary10, label: 'Submit'.addTranslation, maxWidth: true, onPressed: _onSubmit), ), ], ), @@ -348,7 +335,6 @@ class _AssetInventoryFormViewState extends State { Future searchAsset({required String assetNo}) async { AssetInventoryProvider provider = Provider.of(context, listen: false); - Map payload = { "assetNumber": assetNo, "sessionId": widget.assetInventoryModel?.sessionId, @@ -415,11 +401,20 @@ class _AssetInventoryFormViewState extends State { _scannedAssetModel?.departmentId = widget.assetInventoryModel?.department?.id; _scannedAssetModel?.roomId = widget.assetInventoryModel?.room?.id; _scannedAssetModel?.isNotRegistered = isRegistered; - if (attachments.isNotEmpty) { - String? photoName = attachments.first.name; - String fileName = ServiceRequestUtils.isLocalUrl(photoName ?? '') ? ("${photoName ?? ''.split("/").last}|${base64Encode(File(photoName ?? '').readAsBytesSync())}") : photoName ?? ''; - _scannedAssetModel?.photo = fileName; + _scannedAssetModel?.assetInventoryDetailAssetAttachments = []; + // try { + for (var item in attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + _scannedAssetModel?.assetInventoryDetailAssetAttachments.add(AssetInventoryDetailAssetAttachments(id: item.id, name: fileName)); } + // // } catch (error) { + // // print(error); + // // } + // if (attachments.isNotEmpty) { + // String? photoName = attachments.first.name; + // String fileName = ServiceRequestUtils.isLocalUrl(photoName ?? '') ? ("${photoName ?? ''.split("/").last}|${base64Encode(File(photoName ?? '').readAsBytesSync())}") : photoName ?? ''; + // _scannedAssetModel?.photo = fileName; + // } showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); assetInventoryProvider.saveAssetInSession(model: _scannedAssetModel).then((success) async { ///Need to use push and remove until... diff --git a/lib/modules/asset_inventory_module/pages/asset_inventory_page.dart b/lib/modules/asset_inventory_module/pages/asset_inventory_page.dart index 88e21b07..6240373c 100644 --- a/lib/modules/asset_inventory_module/pages/asset_inventory_page.dart +++ b/lib/modules/asset_inventory_module/pages/asset_inventory_page.dart @@ -58,7 +58,7 @@ class _AssetInventoryPageState extends State { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: DefaultAppBar( - title: 'Inventory Session Request'.addTranslation, + title: 'Asset Inventory Session'.addTranslation, onBackPress: () { Navigator.pop(context); }, @@ -86,7 +86,7 @@ class _AssetInventoryPageState extends State { indicator: BoxDecoration(color: context.isDark ? AppColor.neutral60 : AppColor.neutral110, borderRadius: BorderRadius.circular(7)), onTap: (index) {}, tabs: [ - Tab(text: 'Request Details'.addTranslation, height: 57.toScreenHeight), + Tab(text: 'Session Details'.addTranslation, height: 57.toScreenHeight), Tab( text: '${'Scan Assets'.addTranslation} ${provider.assetInventoryResponse?.totalRows != null && provider.assetInventoryResponse!.totalRows! > 0 ? '(${provider.assetInventoryResponse?.totalRows})' : ''}', diff --git a/lib/modules/asset_inventory_module/pages/inventory_session_item_view.dart b/lib/modules/asset_inventory_module/pages/inventory_session_item_view.dart index 8d9abfdd..12adb610 100644 --- a/lib/modules/asset_inventory_module/pages/inventory_session_item_view.dart +++ b/lib/modules/asset_inventory_module/pages/inventory_session_item_view.dart @@ -26,12 +26,6 @@ class InventorySessionItemView extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - StatusLabel( - label: requestData!.priorityName!, - textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.priorityName!), - backgroundColor: AppColor.getRequestStatusColorByName(context, requestData!.priorityName!), - ), - 8.width, StatusLabel( label: requestData!.statusName!, textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.statusName!), @@ -47,7 +41,7 @@ class InventorySessionItemView extends StatelessWidget { ), 8.height, (requestData?.typeTransaction ?? context.translation.taskRequest).heading5(context), - infoWidget(label: context.translation.requestType, value: requestData?.sessionType, context: context), + infoWidget(label: 'Session Type'.addTranslation, value: requestData?.sessionType, context: context), infoWidget(label: 'No of Assets'.addTranslation, value: requestData?.numberOfAssets != null ? requestData?.numberOfAssets.toString() : '-', context: context), infoWidget(label: 'No of Sites'.addTranslation, value: requestData?.numberOfSites != null ? requestData?.numberOfSites.toString() : '-', context: context), 8.height, @@ -73,12 +67,6 @@ class InventorySessionItemView extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - StatusLabel( - label: requestDetails!.priority!, - textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails?.priority!), - backgroundColor: AppColor.getRequestStatusColorByName(context, requestDetails?.priority!), - ), - 8.width, StatusLabel( label: requestDetails!.status!, textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails?.status!), @@ -94,7 +82,7 @@ class InventorySessionItemView extends StatelessWidget { ), 8.height, (requestDetails?.nameOfType ?? context.translation.taskRequest).heading5(context), - infoWidget(label: context.translation.requestType, value: requestDetails?.sessionType, context: context), + infoWidget(label: 'Session Type'.addTranslation, value: requestDetails?.sessionType, context: context), infoWidget(label: 'No of Assets'.addTranslation, value: requestDetails?.numberOfAssets != null ? requestDetails?.numberOfAssets.toString() : '-', context: context), infoWidget(label: 'No of Sites'.addTranslation, value: requestDetails?.numberOfSites != null ? requestDetails?.numberOfSites.toString() : '-', context: context), 8.height, diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index 992c0060..440715e9 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -72,52 +72,44 @@ class _ServiceRequestDetailViewState extends State { ? const CircularProgressIndicator(color: AppColor.primary10).center : requestProvider.currentWorkOrder == null ? const NoDataFound() - : Stack( - children: [ - Column( + : Column( + children: [ + SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - workOrderDetailCard(context, requestProvider.currentWorkOrder!.data!, _userProvider, requestProvider), - initialVisitCard(requestDetailProvider: requestProvider, userProvider: _userProvider), - 12.height, - assetDetailCard(requestDetailProvider: requestProvider, userProvider: _userProvider), - 12.height, - if (context.userProvider.user!.type == UsersTypes.engineer && - !requestProvider.isReadOnlyRequest && - requestProvider.currentWorkOrder!.data!.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity) ...[ - costCard(context, requestProvider), - 12.height, - ], - contactInfoCard(context, requestProvider.currentWorkOrder!.data!), - 20.height, - ], - ), - ).expanded, - SafeArea( - top: false, - right: false, - left: false, - child: FooterActionButton.requestDetailsFooterWidget( - workOrderNextStepStatus: requestProvider.currentWorkOrder!.data!.nextStep!.workOrderNextStepEnum!, - status: requestProvider.currentWorkOrder?.data?.status, - isEmpIsAssigned: requestProvider.currentWorkOrder!.data!.assignedEmployee != null, - activities: requestProvider.currentWorkOrder!.data?.activities ?? [], - userProvider: _userProvider, - context: context), - ).toShadowContainer(context, padding: 0, showShadow: false, borderRadius: 0), + workOrderDetailCard(context, requestProvider.currentWorkOrder!.data!, _userProvider, requestProvider), + initialVisitCard(requestDetailProvider: requestProvider, userProvider: _userProvider), + 12.height, + assetDetailCard(requestDetailProvider: requestProvider, userProvider: _userProvider), + 12.height, + if (context.userProvider.user!.type == UsersTypes.engineer && + !requestProvider.isReadOnlyRequest && + requestProvider.currentWorkOrder!.data!.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity) ...[ + costCard(context, requestProvider), + 12.height, + ], + contactInfoCard(context, requestProvider.currentWorkOrder!.data!), + 20.height, ], ), - //no need to show timer as discussed with backend - // if (requestProvider.timer != null && requestProvider.timer!.isActive) ...[ - // const TimerWidget(), - // ] - ], - ); + ).expanded, + SafeArea( + top: false, + right: false, + left: false, + child: FooterActionButton.requestDetailsFooterWidget( + workOrderNextStepStatus: requestProvider.currentWorkOrder!.data!.nextStep!.workOrderNextStepEnum!, + status: requestProvider.currentWorkOrder?.data?.status, + isEmpIsAssigned: requestProvider.currentWorkOrder!.data!.assignedEmployee != null, + activities: requestProvider.currentWorkOrder!.data?.activities ?? [], + userProvider: _userProvider, + context: context), + ).toShadowContainer(context, padding: 0, showShadow: false, borderRadius: 0), + ], + ); }); } diff --git a/lib/modules/internal_audit_module/models/engineer_data_model.dart b/lib/modules/internal_audit_module/models/engineer_data_model.dart new file mode 100644 index 00000000..dbb34d24 --- /dev/null +++ b/lib/modules/internal_audit_module/models/engineer_data_model.dart @@ -0,0 +1,53 @@ +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; + +class EngineerData { + int? id; + String? debrief; + String? startTime; + String? endTime; + double? totalHours; + bool? isComplete; + int? statusId; + int? requestId; + List? attachments; + + EngineerData({ + this.id, + this.debrief, + this.startTime, + this.endTime, + this.totalHours, + this.isComplete, + this.statusId, + this.requestId, + this.attachments, + }); + + EngineerData.fromJson(Map json) { + id = json['id']; + debrief = json['debrief']; + startTime = json['startTime']; + endTime = json['endTime']; + totalHours = (json['totalHours'] as num?)?.toDouble(); + isComplete = json['isComplete']; + statusId = json['statusId']; + requestId = json['requestId']; + attachments = json['attachments'] != null ? (json['attachments'] as List).map((e) => InternalAuditAttachments.fromJson(e)).toList() : []; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['debrief'] = debrief; + data['startTime'] = startTime; + data['endTime'] = endTime; + data['totalHours'] = totalHours; + data['isComplete'] = isComplete; + data['statusId'] = statusId; + data['requestId'] = requestId; + if (attachments != null) { + data['attachments'] = attachments!.map((e) => e.toJson()).toList(); + } + return data; + } +} \ No newline at end of file diff --git a/lib/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart b/lib/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart new file mode 100644 index 00000000..92f41bab --- /dev/null +++ b/lib/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart @@ -0,0 +1,300 @@ + import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/engineer_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart'; + +class EquipmentInternalAuditDataModel { + int? id; + String? requestNo; + String? createdDate; + int? requestNoSequence; + Auditor? auditor; + Status? status; + Asset? asset; + AssignEmployee? assignEmployee; + dynamic manufacture; + String? remarks; + List? equipmentsFindings; + List? attachments; + EngineerData? engineerData; + + + EquipmentInternalAuditDataModel({ + this.id, + this.requestNo, + this.createdDate, + this.requestNoSequence, + this.auditor, + this.status, + this.asset, + this.assignEmployee, + this.manufacture, + this.remarks, + this.equipmentsFindings, + this.attachments, + this.engineerData, + }); + + EquipmentInternalAuditDataModel.fromJson(Map json) { + id = json['id']; + requestNo = json['requestNo']; + createdDate = json['createdDate']; + requestNoSequence = json['requestNoSequence']; + auditor = json['auditor'] != null ? Auditor.fromJson(json['auditor']) : null; + status = json['status'] != null ? Status.fromJson(json['status']) : null; + asset = json['asset'] != null ? Asset.fromJson(json['asset']) : null; + assignEmployee = json['assignEmployee'] != null ? AssignEmployee.fromJson(json['assignEmployee']) : null; + manufacture = json['manufacture']; + remarks = json['remarks']; + if (json['equipmentsFindings'] != null) { + equipmentsFindings = []; + json['equipmentsFindings'].forEach((v) { + equipmentsFindings!.add(EquipmentsFinding.fromJson(v)); + }); + } + attachments = json['attachments'] != null + ? (json['attachments'] as List) + .map((e) => InternalAuditAttachments.fromJson(e)) + .where((e) => e.name != null) // optional filter if you want to skip null names + .toList() + : []; + engineerData = json['engineerData'] != null ? EngineerData.fromJson(json['engineerData']) : null; + + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['requestNo'] = requestNo; + data['createdDate'] = createdDate; + data['requestNoSequence'] = requestNoSequence; + if (auditor != null) { + data['auditor'] = auditor!.toJson(); + } + if (status != null) { + data['status'] = status!.toJson(); + } + if (asset != null) { + data['asset'] = asset!.toJson(); + } + if (assignEmployee != null) { + data['assignEmployee'] = assignEmployee!.toJson(); + } + data['manufacture'] = manufacture; + data['remarks'] = remarks; + if (equipmentsFindings != null) { + data['equipmentsFindings'] = + equipmentsFindings!.map((v) => v.toJson()).toList(); + } + data['attachments'] = attachments; + if (engineerData != null) data['engineerData'] = engineerData!.toJson(); + return data; + } +} + +class Auditor { + String? userId; + String? userName; + String? email; + String? employeeId; + int? languageId; + String? extensionNo; + String? phoneNumber; + bool? isActive; + + Auditor({ + this.userId, + this.userName, + this.email, + this.employeeId, + this.languageId, + this.extensionNo, + this.phoneNumber, + this.isActive, + }); + + Auditor.fromJson(Map json) { + userId = json['userId']; + userName = json['userName']; + email = json['email']; + employeeId = json['employeeId']; + languageId = json['languageId']; + extensionNo = json['extensionNo']; + phoneNumber = json['phoneNumber']; + isActive = json['isActive']; + } + + Map toJson() { + final Map data = {}; + data['userId'] = userId; + data['userName'] = userName; + data['email'] = email; + data['employeeId'] = employeeId; + data['languageId'] = languageId; + data['extensionNo'] = extensionNo; + data['phoneNumber'] = phoneNumber; + data['isActive'] = isActive; + return data; + } +} + +class Status { + int? id; + String? name; + int? value; + + Status({this.id, this.name, this.value}); + + Status.fromJson(Map json) { + id = json['id']; + name = json['name']; + value = json['value']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['value'] = value; + return data; + } +} + +class Asset { + int? id; + String? assetNumber; + String? assetSerialNo; + String? assetName; + String? modelName; + String? manufacturerName; + String? siteName; + int? siteId; + String? modelDefinition; + + Asset({ + this.id, + this.assetNumber, + this.assetSerialNo, + this.assetName, + this.modelName, + this.manufacturerName, + this.siteName, + this.siteId, + this.modelDefinition, + }); + + Asset.fromJson(Map json) { + id = json['id']; + assetNumber = json['assetNumber']; + assetSerialNo = json['assetSerialNo']; + assetName = json['assetName']; + modelName = json['modelName']; + manufacturerName = json['manufacturerName']; + siteName = json['siteName']; + siteId = json['siteId']; + modelDefinition = json['modelDefinition']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['assetNumber'] = assetNumber; + data['assetSerialNo'] = assetSerialNo; + data['assetName'] = assetName; + data['modelName'] = modelName; + data['manufacturerName'] = manufacturerName; + data['siteName'] = siteName; + data['siteId'] = siteId; + data['modelDefinition'] = modelDefinition; + return data; + } +} + +class AssignEmployee { + String? userId; + String? userName; + String? email; + String? employeeId; + int? languageId; + String? extensionNo; + String? phoneNumber; + bool? isActive; + + AssignEmployee({ + this.userId, + this.userName, + this.email, + this.employeeId, + this.languageId, + this.extensionNo, + this.phoneNumber, + this.isActive, + }); + + AssignEmployee.fromJson(Map json) { + userId = json['userId']; + userName = json['userName']; + email = json['email']; + employeeId = json['employeeId']; + languageId = json['languageId']; + extensionNo = json['extensionNo']; + phoneNumber = json['phoneNumber']; + isActive = json['isActive']; + } + + Map toJson() { + final Map data = {}; + data['userId'] = userId; + data['userName'] = userName; + data['email'] = email; + data['employeeId'] = employeeId; + data['languageId'] = languageId; + data['extensionNo'] = extensionNo; + data['phoneNumber'] = phoneNumber; + data['isActive'] = isActive; + return data; + } +} + +class EquipmentsFinding { + int? id; + Finding? finding; + + EquipmentsFinding({this.id, this.finding}); + + EquipmentsFinding.fromJson(Map json) { + id = json['id']; + finding = json['finding'] != null ? Finding.fromJson(json['finding']) : null; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + if (finding != null) { + data['finding'] = finding!.toJson(); + } + return data; + } +} + +class Finding { + int? id; + String? name; + int? value; + + Finding({this.id, this.name, this.value}); + + Finding.fromJson(Map json) { + id = json['id']; + name = json['name']; + value = json['value']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['value'] = value; + return data; + } +} diff --git a/lib/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart b/lib/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart new file mode 100644 index 00000000..e6a79e91 --- /dev/null +++ b/lib/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart @@ -0,0 +1,41 @@ +import 'package:flutter/src/widgets/framework.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/models/device/asset.dart'; +import 'package:test_sa/models/fault_description.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; + +class EquipmentInternalAuditFormModel { + int? id=0; + String? deviceArName; + String? auditorId; + String? woOrderNo; + List? devicePhotos; + List findings =[]; + List attachments = []; + String? remarks; + Asset? device; + EquipmentInternalAuditFormModel({ + this.id, + this.deviceArName, + this.auditorId, + this.devicePhotos, + this.woOrderNo, + this.remarks, + }); + + Map toJson() { + return { + 'id': id??0, + 'assetId': device?.id, + 'auditorId': auditorId, + 'findings': findings.map((e) => {'findingId': e.value}).toList(), + 'attachments': attachments.map((e) => e.toJson()).toList(), + 'remarks': remarks, + }; + } + + +} \ No newline at end of file diff --git a/lib/modules/internal_audit_module/models/internal_audit_attachment_model.dart b/lib/modules/internal_audit_module/models/internal_audit_attachment_model.dart new file mode 100644 index 00000000..5e21d5ae --- /dev/null +++ b/lib/modules/internal_audit_module/models/internal_audit_attachment_model.dart @@ -0,0 +1,27 @@ +import 'dart:developer'; + +class InternalAuditAttachments { + InternalAuditAttachments({this.id, this.originalName, this.name, this.createdBy}); + + int? id; + String? name; + String? originalName; + String? createdBy; + + InternalAuditAttachments.fromJson(Map json) { + log('name is ${json['name']}'); + id = json['id']; + name = (json['name'] != null && !json['name'].toString().startsWith('data:image/jpeg')) ? json['name'] : null; + originalName = json['originalName']; + createdBy = json['createdBy']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['originalName'] = originalName; + // data['createdBy'] = createdBy; + return data; + } +} diff --git a/lib/modules/internal_audit_module/models/internal_audit_timer_model.dart b/lib/modules/internal_audit_module/models/internal_audit_timer_model.dart new file mode 100644 index 00000000..708ab2d1 --- /dev/null +++ b/lib/modules/internal_audit_module/models/internal_audit_timer_model.dart @@ -0,0 +1,35 @@ +class InternalAuditTimerModel { + int? id; + String? startDate; + String? endDate; + double? totalWorkingHour; + String? comment; + + InternalAuditTimerModel({ + this.id, + this.startDate, + this.endDate, + this.totalWorkingHour, + this.comment, + }); + + factory InternalAuditTimerModel.fromJson(Map json) { + return InternalAuditTimerModel( + id: json['id'] as int?, + startDate: json['startDate'], + endDate: json['endDate'], + totalWorkingHour: (json['totalWorkingHours'] as num?)?.toDouble(), + comment: json['comment'] as String?, + ); + } + + Map toJson() { + return { + 'id': id, + 'startDate': startDate, + 'endDate': endDate, + 'totalWorkingHours': totalWorkingHour, + 'comment': comment, + }; + } +} \ No newline at end of file diff --git a/lib/modules/internal_audit_module/models/system_internal_audit_data_model.dart b/lib/modules/internal_audit_module/models/system_internal_audit_data_model.dart new file mode 100644 index 00000000..0546ccdc --- /dev/null +++ b/lib/modules/internal_audit_module/models/system_internal_audit_data_model.dart @@ -0,0 +1,138 @@ +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/internal_audit_module/models/engineer_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart'; + +class SystemInternalAuditDataModel { + int? id; + int? assetGroupId; + String? requestNo; + int? requestNoSequence; + Auditor? auditor; + Lookup? status; + Lookup? findingType; + String? findingDescription; + Lookup? workOrderType; + Auditor? assignEmployee; + SystemAuditWorkOrderDetailModel? workOrderDetails; // Already exists as a separate model + String? createdBy; + String? createdDate; + String? modifiedBy; + String? modifiedDate; + List? attachments; + EngineerData? engineerData; + + SystemInternalAuditDataModel({ + this.id, + this.assetGroupId, + this.requestNo, + this.requestNoSequence, + this.auditor, + this.status, + this.findingType, + this.findingDescription, + this.workOrderType, + this.assignEmployee, + this.workOrderDetails, + this.createdBy, + this.createdDate, + this.modifiedBy, + this.modifiedDate, + this.attachments, + this.engineerData, + }); + + SystemInternalAuditDataModel.fromJson(Map json) { + id = json['id']; + assetGroupId = json['assetGroupId']; + requestNo = json['requestNo']; + requestNoSequence = json['requestNoSequence']; + auditor = json['auditor'] != null ? Auditor.fromJson(json['auditor']) : null; + status = json['status'] != null ? Lookup.fromJson(json['status']) : null; + findingType = json['findingType'] != null ? Lookup.fromJson(json['findingType']) : null; + findingDescription = json['findingDescription']; + workOrderType = json['workOrderType'] != null ? Lookup.fromJson(json['workOrderType']) : null; + assignEmployee = json['assignEmployee'] != null ? Auditor.fromJson(json['assignEmployee']) : null; + workOrderDetails = json['workOrderDetails'] != null ? SystemAuditWorkOrderDetailModel.fromJson(json['workOrderDetails']) : null; // Keep as-is + createdBy = json['createdBy']; + createdDate = json['createdDate']; + modifiedBy = json['modifiedBy']; + modifiedDate = json['modifiedDate']; + attachments = json['attachments'] != null + ? (json['attachments'] as List) + .map((e) => InternalAuditAttachments.fromJson(e)) + .where((e) => e.name != null) // optional filter if you want to skip null names + .toList() + : []; + engineerData = json['engineerData'] != null ? EngineerData.fromJson(json['engineerData']) : null; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['assetGroupId'] = assetGroupId; + data['requestNo'] = requestNo; + data['requestNoSequence'] = requestNoSequence; + if (auditor != null) data['auditor'] = auditor!.toJson(); + if (status != null) data['status'] = status!.toJson(); + if (findingType != null) data['findingType'] = findingType!.toJson(); + data['findingDescription'] = findingDescription; + if (workOrderType != null) data['workOrderType'] = workOrderType!.toJson(); + if (assignEmployee != null) data['assignEmployee'] = assignEmployee!.toJson(); + data['workOrderDetails'] = workOrderDetails; + data['createdBy'] = createdBy; + data['createdDate'] = createdDate; + data['modifiedBy'] = modifiedBy; + data['modifiedDate'] = modifiedDate; + if (engineerData != null) data['engineerData'] = engineerData!.toJson(); + return data; + } +} + +class Auditor { + String? userId; + String? userName; + String? email; + String? employeeId; + int? languageId; + String? extensionNo; + String? phoneNumber; + bool? isActive; + + Auditor({ + this.userId, + this.userName, + this.email, + this.employeeId, + this.languageId, + this.extensionNo, + this.phoneNumber, + this.isActive, + }); + + Auditor.fromJson(Map json) { + userId = json['userId']; + userName = json['userName']; + email = json['email']; + employeeId = json['employeeId']; + languageId = json['languageId']; + extensionNo = json['extensionNo']; + phoneNumber = json['phoneNumber']; + isActive = json['isActive']; + } + + Map toJson() { + final Map data = {}; + data['userId'] = userId; + data['userName'] = userName; + data['email'] = email; + data['employeeId'] = employeeId; + data['languageId'] = languageId; + data['extensionNo'] = extensionNo; + data['phoneNumber'] = phoneNumber; + data['isActive'] = isActive; + return data; + } +} + + diff --git a/lib/modules/internal_audit_module/models/system_internal_audit_form_model.dart b/lib/modules/internal_audit_module/models/system_internal_audit_form_model.dart new file mode 100644 index 00000000..3fda34d0 --- /dev/null +++ b/lib/modules/internal_audit_module/models/system_internal_audit_form_model.dart @@ -0,0 +1,202 @@ +import 'dart:developer'; + +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; + +class SystemInternalAuditFormModel { + int? id; + String? auditorId; + String? assignEmployeeId; + Lookup? findingType; + String? findingDescription; + Lookup? workOrderType; + int? correctiveMaintenanceId; + int? planPreventiveVisitId; + int? assetTransferId; + List? attachments = []; + int? taskJobId; + int? taskAlertJobId; + int? gasRefillId; + int? planRecurrentTaskId; + int? statusId; + SystemAuditWorkOrderDetailModel? workOrderDetailModel; + + SystemInternalAuditFormModel({ + this.id, + this.auditorId, + this.assignEmployeeId, + this.findingType, + this.findingDescription, + this.workOrderType, + this.correctiveMaintenanceId, + this.planPreventiveVisitId, + this.assetTransferId, + this.taskJobId, + this.taskAlertJobId, + this.gasRefillId, + this.planRecurrentTaskId, + this.workOrderDetailModel, + this.statusId, + this.attachments, + }); + + Map toJson() { + return { + // 'id': id, + 'auditorId': auditorId, + 'assignEmployeeId': assignEmployeeId, + 'findingTypeId': findingType?.id, + 'findingDescription': findingDescription, + 'workOrderTypeId': workOrderType?.id, + 'correctiveMaintenanceId': correctiveMaintenanceId, + 'planPreventiveVisitId': planPreventiveVisitId, + 'assetTransferId': assetTransferId, + 'taskJobId': taskJobId, + 'taskAlertJobId': taskAlertJobId, + 'gasRefillId': gasRefillId, + 'planRecurrentTaskId': planRecurrentTaskId, + 'statusId': statusId, + 'attachments': attachments?.map((e) => e.toJson()).toList(), + }; + } +} + +class WoAutoCompleteModel { + int? id; + String? workOrderNo; + String? displayName; + + WoAutoCompleteModel({this.id, this.workOrderNo, this.displayName}); + + WoAutoCompleteModel.fromJson(Map json) { + id = json['id']; + workOrderNo = json['workOrderNo']; + displayName = json['workOrderNo']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['woOrderNo'] = workOrderNo; + data['displayName'] = workOrderNo; + return data; + } +} + +class SystemAuditWorkOrderDetailModel { + String? createdBy; + DateTime? createdDate; + String? modifiedBy; + DateTime? modifiedDate; + int? id; + String? workOrderNo; + String? woAssignedEngineer; + String? woAssetNo; + String? wosn; + String? woAssetName; + String? woModel; + String? woManufacturer; + String? woSite; + String? woDepartment; + String? wOPMScheduleDate; + String? wOPMActualVisitDate; + String? wOAssetTransferDestinationSite; + String? wOAssetTransferSenderEngineer; + String? wOAssetTransferReceiverEngineer; + String? wOTaskAlertJobImpactedStatus; + String? wOTaskAlertJobAcknowledgment; + String? wOGasRefillType; + String? wORecurrentTaskTaskType; + String? wORecurrentTaskPlanNo; + String? wORecurrentTaskPlanName; + String? wORecurrentTaskStatus; + + SystemAuditWorkOrderDetailModel({ + this.createdBy, + this.createdDate, + this.modifiedBy, + this.modifiedDate, + this.id, + this.workOrderNo, + this.woAssignedEngineer, + this.woAssetNo, + this.wosn, + this.woAssetName, + this.woModel, + this.woManufacturer, + this.woSite, + this.woDepartment, + this.wOPMScheduleDate, + this.wOPMActualVisitDate, + this.wOAssetTransferDestinationSite, + this.wOAssetTransferSenderEngineer, + this.wOAssetTransferReceiverEngineer, + this.wOTaskAlertJobImpactedStatus, + this.wOTaskAlertJobAcknowledgment, + this.wOGasRefillType, + this.wORecurrentTaskTaskType, + this.wORecurrentTaskPlanNo, + this.wORecurrentTaskPlanName, + this.wORecurrentTaskStatus, + }); + + SystemAuditWorkOrderDetailModel.fromJson(Map json) { + createdBy = json['createdBy']; + createdDate = json['createdDate'] != null ? DateTime.tryParse(json['createdDate']) : null; + modifiedBy = json['modifiedBy']; + modifiedDate = json['modifiedDate'] != null ? DateTime.tryParse(json['modifiedDate']) : null; + id = json['id']; + workOrderNo = json['workOrderNo']; + woAssignedEngineer = json['woAssignedEngineer']; + woAssetNo = json['woAssetNo']; + wosn = json['wosn']; + woAssetName = json['woAssetName']; + woModel = json['woModel']; + woManufacturer = json['woManufacturer']; + woSite = json['woSite']; + woDepartment = json['woDepartment']; + wOPMScheduleDate = json['wO_PM_ScheduleDate']; + wOPMActualVisitDate = json['wO_PM_ActualVisitDate']; + wOAssetTransferDestinationSite = json['wO_AssetTransfer_DestinationSite']; + wOAssetTransferSenderEngineer = json['wO_AssetTransfer_SenderEngineer']; + wOAssetTransferReceiverEngineer = json['wO_AssetTransfer_ReceiverEngineer']; + wOTaskAlertJobImpactedStatus = json['wO_TaskAlertJob_ImpactedStatus']; + wOTaskAlertJobAcknowledgment = json['wO_TaskAlertJob_Acknowledgment']; + wOGasRefillType = json['wO_GasRefill_Type']; + wORecurrentTaskTaskType = json['wO_RecurrentTask_TaskType']; + wORecurrentTaskPlanNo = json['wO_RecurrentTask_PlanNo']; + wORecurrentTaskPlanName = json['wO_RecurrentTask_PlanName']; + wORecurrentTaskStatus = json['wO_RecurrentTask_Status']; + } + + Map toJson() { + return { + 'createdBy': createdBy, + 'createdDate': createdDate?.toIso8601String(), + 'modifiedBy': modifiedBy, + 'modifiedDate': modifiedDate?.toIso8601String(), + 'id': id, + 'workOrderNo': workOrderNo, + 'woAssignedEngineer': woAssignedEngineer, + 'woAssetNo': woAssetNo, + 'wosn': wosn, + 'woAssetName': woAssetName, + 'woModel': woModel, + 'woManufacturer': woManufacturer, + 'woSite': woSite, + 'woDepartment': woDepartment, + 'wO_PM_ScheduleDate': wOPMScheduleDate, + 'wO_PM_ActualVisitDate': wOPMActualVisitDate, + 'wO_AssetTransfer_DestinationSite': wOAssetTransferDestinationSite, + 'wO_AssetTransfer_SenderEngineer': wOAssetTransferSenderEngineer, + 'wO_AssetTransfer_ReceiverEngineer': wOAssetTransferReceiverEngineer, + 'wO_TaskAlertJob_ImpactedStatus': wOTaskAlertJobImpactedStatus, + 'wO_TaskAlertJob_Acknowledgment': wOTaskAlertJobAcknowledgment, + 'wO_GasRefill_Type': wOGasRefillType, + 'wO_RecurrentTask_TaskType': wORecurrentTaskTaskType, + 'wO_RecurrentTask_PlanNo': wORecurrentTaskPlanNo, + 'wO_RecurrentTask_PlanName': wORecurrentTaskPlanName, + 'wO_RecurrentTask_Status': wORecurrentTaskStatus, + }; + } +} diff --git a/lib/modules/internal_audit_module/models/update_audit_form_model.dart b/lib/modules/internal_audit_module/models/update_audit_form_model.dart new file mode 100644 index 00000000..6942d51f --- /dev/null +++ b/lib/modules/internal_audit_module/models/update_audit_form_model.dart @@ -0,0 +1,61 @@ +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart'; + +class AuditFormModel { + int? id; + String? createdDate; + int? requestId; + String? debrief; + DateTime? startTime; + DateTime? endTime; + double? totalHours; + List? attachments; + bool? isComplete; + TimerModel? auditTimerModel = TimerModel(); + List? auditTimers = []; + TimerModel? auditTimePicker; + List? timerModelList = []; + + AuditFormModel( + {this.id, + this.requestId, + this.debrief, + this.startTime, + this.endTime, + this.totalHours, + this.createdDate, + this.attachments, + this.isComplete, + this.auditTimerModel, + this.timerModelList, + this.auditTimePicker, + this.auditTimers}); + + AuditFormModel.fromJson(Map json) { + id = json['id']; + requestId = json['requestId']; + debrief = json['debrief']; + startTime = json['startTime'] != null ? DateTime.tryParse(json['startTime']) : null; + endTime = json['endTime'] != null ? DateTime.tryParse(json['endTime']) : null; + totalHours = json['totalHours']?.toDouble(); + if (json['attachments'] != null) { + attachments = (json['attachments'] as List).map((e) => InternalAuditAttachments.fromJson(e)).toList(); + } + isComplete = json['isComplete']; + } + + Map toJson() { + return { + 'id': id, + 'requestId': requestId, + 'debrief': debrief, + 'startTime': startTime?.toIso8601String(), + 'endTime': endTime?.toIso8601String(), + // 'auditTimer': auditTimers, + 'totalHours': totalHours, + 'attachments': attachments?.map((e) => e.toJson()).toList(), + 'isComplete': isComplete, + }; + } +} diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart new file mode 100644 index 00000000..532a5b14 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/create_equipment_internal_audit_form.dart @@ -0,0 +1,147 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/new_views/common_widgets/multiple_item_drop_down_menu.dart'; +import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; + +class CreateEquipmentInternalAuditForm extends StatefulWidget { + static const String id = "/create-equipment-internal-audit-form"; + + const CreateEquipmentInternalAuditForm({Key? key}) : super(key: key); + + @override + _CreateEquipmentInternalAuditFormState createState() => _CreateEquipmentInternalAuditFormState(); +} + +class _CreateEquipmentInternalAuditFormState extends State with TickerProviderStateMixin { + late TextEditingController _commentController; + final EquipmentInternalAuditFormModel _equipmentinternalAuditModel = EquipmentInternalAuditFormModel(); + final List _attachments = []; + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + FocusNode auditCheckListNode = FocusNode(); + + @override + void initState() { + super.initState(); + _commentController = TextEditingController(); + } + + @override + void dispose() { + _commentController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: DefaultAppBar(title: 'Equipment Internal Audit Checklist'.addTranslation), + body: Form( + key: _formKey, + child: Column( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AssetPicker( + device: _equipmentinternalAuditModel.device, + borderColor: AppColor.black20, + buttonColor: AppColor.white936, + onPick: (asset) async { + _equipmentinternalAuditModel.device = asset; + setState(() {}); + }, + ), + 16.height, + //TODO need to check layout issue... + MultipleItemDropDownMenu( + context: context, + showAsBottomSheet: true, + backgroundColor: AppColor.neutral100, + showShadow: false, + showCancel: true, + requestById: context.userProvider.user?.clientId, + title: 'Internal Audit Checklist'.addTranslation, + initialValue: _equipmentinternalAuditModel.findings, + onSelect: (value) { + _equipmentinternalAuditModel.findings = value ?? []; + }, + ), + 16.height, + AppTextFormField( + backgroundColor: AppColor.fieldBgColor(context), + labelText: 'Remarks'.addTranslation, + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + alignLabelWithHint: true, + textInputType: TextInputType.multiline, + showShadow: false, + onSaved: (value) { + _equipmentinternalAuditModel.remarks = value; + }, + ), + 16.height, + AttachmentPicker( + label: context.translation.attachments, + attachment: _attachments, + buttonColor: AppColor.primary10, + onlyImages: false, + onChange: (value) {}, + buttonIcon: 'attachment_icon'.toSvgAsset(color: AppColor.primary10), + ), + ], + ).toShadowContainer(context), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton(label: context.translation.submitRequest, buttonColor: AppColor.primary10, onPressed: _submit), + ), + ], + ), + ), + ); + } + + Future _submit() async { + InternalAuditProvider internalAuditProvider = Provider.of(context, listen: false); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + _equipmentinternalAuditModel.attachments = []; + for (var item in _attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + _equipmentinternalAuditModel.attachments.add(InternalAuditAttachments(id: item.id, name: fileName)); + } + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + _equipmentinternalAuditModel.auditorId = context.userProvider.user?.userID; + bool status = await internalAuditProvider.addEquipmentInternalAudit(context: context, request: _equipmentinternalAuditModel); + Navigator.pop(context); + if(status){ + Navigator.pop(context); + } + } + } +} diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart new file mode 100644 index 00000000..9d18f9fd --- /dev/null +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart @@ -0,0 +1,207 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; +import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; + +class EquipmentInternalAuditDetailPage extends StatefulWidget { + static const String id = "/details-internal-audit"; + + final int auditId; + + const EquipmentInternalAuditDetailPage({Key? key, required this.auditId}) : super(key: key); + + @override + _EquipmentInternalAuditDetailPageState createState() { + return _EquipmentInternalAuditDetailPageState(); + } +} + +class _EquipmentInternalAuditDetailPageState extends State { + bool isWoType = true; + EquipmentInternalAuditDataModel? model; + late InternalAuditProvider _internalAuditProvider; + List allAttachments = []; + + @override + void initState() { + super.initState(); + _internalAuditProvider = Provider.of(context, listen: false); + WidgetsBinding.instance.addPostFrameCallback((_) { + getAuditData(); + }); + } + + Future getAuditData() async { + model = await _internalAuditProvider.getEquipmentInternalAuditById(widget.auditId); + allAttachments.clear(); + allAttachments = [ + ...(model?.attachments ?? []), + ...(model?.engineerData?.attachments ?? []), + ]; + + + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + //TODO need to check when implementing provider needed or not . + return Scaffold( + appBar: const DefaultAppBar(title: "Request Details"), + body: Selector( + selector: (_, provider) => provider.isLoading, + builder: (_, isLoading, __) { + if (isLoading) return const ALoading(); + if (model == null) return NoDataFound(message: context.translation.noDataFound).center; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + assetDetails(), + 8.height, + requestDetails(), + 8.height, + if (model?.remarks?.isNotEmpty ?? false) ...[ + const Divider().defaultStyle(context), + model!.remarks!.bodyText(context), + 8.height, + ], + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 8.height, + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.name ?? '') ?? '').toList() ?? []), + ], + ], + ).paddingAll(0).toShadowContainer(context), + ).expanded, + if (context.userProvider.isEngineer) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Update", + onPressed: () async { + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => UpdateEquipmentInternalAuditPage(model: model), + ), + ); + if (result == true) { + await getAuditData(); + setState(() {}); // refresh UI with new model + } + // Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdateEquipmentInternalAuditPage(model: model))); + }), + ), + ], + ); + }, + )); + } + + Widget workOrderInformation() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text( + "WO Info", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + '${context.translation.woNumber}: ${'-'}'.bodyText(context), + '${'WO Type'.addTranslation}: ${'-'}'.bodyText(context), + '${context.translation.site}: ${'-'}'.bodyText(context), + '${context.translation.assetName}: ${'-'}'.bodyText(context), + '${context.translation.manufacture}: ${'-'}'.bodyText(context), + '${context.translation.model}: ${'-'}'.bodyText(context), + ], + ); + } + + Widget requestDetails() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text( + "Request Details", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + if (model != null && model!.equipmentsFindings != null && model!.equipmentsFindings!.isNotEmpty) + ...model!.equipmentsFindings!.map((item) { + final findingName = item.finding?.name ?? 'Unknown'; + return checklistWidget(value: findingName.addTranslation); + }).toList() + else + Text('No findings available'.addTranslation), + ], + ); + } + + Widget assetDetails() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Asset Details", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + '${context.translation.assetName}: ${model?.asset?.assetName ?? '-'}'.bodyText(context), + '${context.translation.assetNo}: ${model?.asset?.assetNumber ?? '-'}'.bodyText(context), + '${context.translation.manufacture}: ${model?.asset?.manufacturerName ?? '-'}'.bodyText(context), + '${context.translation.model}: ${model?.asset?.modelName ?? '-'}'.bodyText(context), + ], + ); + } + + Widget checklistWidget({required String value}) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: true, + activeColor: AppColor.neutral120, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: const VisualDensity(horizontal: -4, vertical: -3), + onChanged: (value) {}, + ), + value.bodyText(context), + ], + ); + } +} diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart new file mode 100644 index 00000000..a17407a2 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart @@ -0,0 +1,115 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/all_requests_and_count_model.dart'; +import 'package:test_sa/models/new_models/dashboard_detail.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +class EquipmentInternalAuditItemView extends StatelessWidget { + final Data? requestData; + final RequestsDetails? requestDetails; + final bool showShadow; + + const EquipmentInternalAuditItemView({Key? key, this.requestData, this.requestDetails, this.showShadow = true}) : super(key: key); + + @override + Widget build(BuildContext context) { + //TODO need to refactor this code repetation @waseem + if (requestData != null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: requestData!.statusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.statusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, requestData!.statusName!), + ), + 1.width.expanded, + Text( + requestData!.transactionDate?.toServiceRequestCardFormat ?? "", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 8.height, + (requestData?.typeTransaction ?? "Internal Audit Request").heading5(context), + infoWidget(label: context.translation.assetNumber, value: requestData?.assetNumber, context: context), + + 8.height, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.translation.viewDetails, + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)), + ), + 4.width, + Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14) + ], + ), + ], + ).toShadowContainer(context, withShadow: showShadow).onPress(() async { + Navigator.push(context, MaterialPageRoute(builder: (context) => EquipmentInternalAuditDetailPage(auditId: requestDetails!.id!))); + }); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: requestDetails!.status!, + textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails?.status!), + backgroundColor: AppColor.getRequestStatusColorByName(context, requestDetails?.status!), + ), + 1.width.expanded, + Text( + requestDetails!.date?.toServiceRequestCardFormat ?? "", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 8.height, + (requestDetails?.nameOfType ?? "Internal Audit Request").heading5(context), + 8.height, + infoWidget(label: context.translation.assetNumber, value: requestDetails!.assetNo, context: context), + infoWidget(label: context.translation.assetSN, value: requestDetails!.assetSN, context: context), + infoWidget(label: context.translation.model, value: requestDetails!.model, context: context), + 8.height, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.translation.viewDetails, + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)), + ), + 4.width, + Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14) + ], + ), + ], + ).toShadowContainer(context, withShadow: showShadow).onPress(() async { + Navigator.push(context, MaterialPageRoute(builder: (context) => EquipmentInternalAuditDetailPage(auditId: requestDetails!.id!))); + }); + } + + Widget infoWidget({required String label, String? value, required BuildContext context}) { + if (value != null && value.isNotEmpty) { + return '$label: $value'.bodyText(context); + } + return const SizedBox(); + } +} diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart new file mode 100644 index 00000000..f69e4a10 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart @@ -0,0 +1,322 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/new_views/common_widgets/working_time_tile.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart'; + +class UpdateEquipmentInternalAuditPage extends StatefulWidget { + static const String id = "update-equipment-internal-audit"; + EquipmentInternalAuditDataModel? model; + + UpdateEquipmentInternalAuditPage({Key? key, this.model}) : super(key: key); + + @override + State createState() => _UpdateEquipmentInternalAuditPageState(); +} + +class _UpdateEquipmentInternalAuditPageState extends State { + final bool _isLoading = false; + double totalWorkingHours = 0.0; + AuditFormModel formModel = AuditFormModel(); + final TextEditingController _workingHoursController = TextEditingController(); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + List _attachments = []; + + //TODO need to check if it's needed or not.. + List timerList = []; + + @override + void initState() { + populateForm(); + super.initState(); + } + + void populateForm() { + formModel.requestId = widget.model?.id; + formModel.id = widget.model?.id; + _attachments = widget.model?.engineerData?.attachments?.map((e) => GenericAttachmentModel(id: e.id?.toInt() ?? 0, name: e.name!)).toList() ?? []; + + calculateWorkingTime(); + } + + void calculateWorkingTime() { + final helperModel = formModel; + final timers = helperModel.auditTimers ?? []; + totalWorkingHours = timers.fold(0.0, (sum, item) { + if (item.startDate == null || item.endDate == null) return sum; + try { + final start = DateTime.parse(item.startDate!); + final end = DateTime.parse(item.endDate!); + final diffInHours = end.difference(start).inSeconds / 3600.0; // convert to hours + return sum + diffInHours; + } catch (_) { + return sum; + } + }); + timerList = timers.map((e) { + return TimerHistoryModel( + id: e.id, + startTime: e.startDate, + endTime: e.endDate, + workingHours: e.startDate, + ); + }).toList(); + } + + _onSubmit(BuildContext context) async { + bool isTimerPickerEnable = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; + InternalAuditProvider provider = Provider.of(context, listen: false); + + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + formModel.auditTimers ??= []; + if (formModel.auditTimePicker != null) { + int durationInSecond = formModel.auditTimePicker!.endAt!.difference(formModel.auditTimePicker!.startAt!).inSeconds; + formModel.auditTimers?.add( + InternalAuditTimerModel( + id: 0, + startDate: formModel.auditTimePicker!.startAt!.toIso8601String(), // Handle potential null + endDate: formModel.auditTimePicker!.endAt?.toIso8601String(), // Handle potential null + totalWorkingHour: ((durationInSecond) / 60 / 60), + ), + ); + } + formModel.timerModelList?.forEach((timer) { + int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; + formModel.auditTimers?.add( + InternalAuditTimerModel( + id: 0, + startDate: timer.startAt!.toIso8601String(), // Handle potential null + endDate: timer.endAt?.toIso8601String(), // Handle potential null + totalWorkingHour: ((durationInSecond) / 60 / 60), + ), + ); + }); + + _formKey.currentState!.save(); + formModel.attachments = []; + for (var item in _attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name?.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + formModel.attachments!.add( + InternalAuditAttachments( + id: item.id, + originalName: fileName, + name: fileName, + ), + ); + } + final success = await provider.updateEquipmentInternalAudit(model: formModel); + Navigator.pop(context); + if (formModel.isComplete == true) { + //Navigate to List screen. + } else if (formModel.isComplete == false) { + //Navigate to Detail screen. + if (success) { + Navigator.of(context).pop(true); + } else { + Fluttertoast.showToast(msg: 'Failed to update'); + } + + } + } + + @override + void dispose() { + _workingHoursController.dispose(); + super.dispose(); + } + + void updateTimer({TimerModel? timer}) { + if (timer?.startAt != null && timer?.endAt != null) { + final start = timer!.startAt!; + final end = timer.endAt!; + + final difference = end.difference(start); + final totalHours = difference.inSeconds / 3600.0; + formModel.startTime = start; + formModel.endTime = end; + formModel.totalHours = totalHours; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: DefaultAppBar( + title: 'Update Information'.addTranslation, + onWillPopScope: () { + formModel.isComplete = false; + _onSubmit(context); + }, + ), + key: _scaffoldKey, + body: Form( + key: _formKey, + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + 8.height, + AppTextFormField( + labelText: 'Debrief'.addTranslation, + textInputType: TextInputType.multiline, + hintStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10), + labelStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10), + alignLabelWithHint: true, + initialValue: formModel.debrief, + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + onSaved: (value) { + formModel.debrief = value; + }, + ), + 8.height, + _timerWidget(context, totalWorkingHours), + 16.height, + AttachmentPicker( + label: 'Upload Attachment', + attachment: _attachments, + buttonColor: AppColor.primary10, + onlyImages: false, + buttonIcon: 'attachment_icon'.toSvgAsset( + color: AppColor.primary10, + ), + ), + 8.height, + ], + ).toShadowContainer(context), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + AppFilledButton( + label: context.translation.save, + buttonColor: context.isDark ? AppColor.neutral70 : AppColor.white60, + textColor: context.isDark ? AppColor.white10 : AppColor.black10, + onPressed: () { + formModel.isComplete = false; + _onSubmit(context); + }).expanded, + 12.width, + AppFilledButton( + label: context.translation.complete, + buttonColor: AppColor.primary10, + onPressed: () { + formModel.isComplete = true; + _onSubmit(context); + }).expanded, + ], + ), + ), + ], + )), + ), + ).handlePopScope( + cxt: context, + onSave: () { + formModel.isComplete = false; + _onSubmit(context); + }); + } + + Widget _timerWidget(BuildContext context, double totalWorkingHours) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + //TODO multiple timer .. + // AppTimer( + // label: context.translation.timer, + // timer: formModel.auditTimerModel, + // // enabled: enableTimer, + // pickerTimer: formModel.auditTimePicker, + // pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''), + // onPick: (time) { + // formModel.auditTimePicker = time; + // setState(() {}); + // log('Time picker start ${formModel.auditTimePicker?.startAt}'); + // log('Time picker end ${formModel.auditTimePicker?.endAt}'); + // }, + // timerProgress: (isRunning) {}, + // onChange: (timer) async { + // formModel.auditTimerModel = timer; + // log('start ${formModel.auditTimerModel?.startAt}'); + // log('end ${formModel.auditTimerModel?.endAt}'); + // if (timer.startAt != null && timer.endAt != null) { + // formModel.timerModelList = formModel.timerModelList ?? []; + // formModel.timerModelList!.add(timer); + // } + // setState(() {}); + // log('list length ${formModel.timerModelList?.length}'); + // return true; + // }, + // ), + AppTimer( + label: context.translation.workingHours, + timer: formModel.auditTimerModel, + pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''), + pickerTimer: formModel.auditTimePicker, + showPicker: false, + onPick: (timer) { + updateTimer(timer: timer); + }, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(10), + ), + timerProgress: (isRunning) {}, + onChange: (timer) async { + updateTimer(timer: timer); + log('here onChange ${timer.startAt}'); + + return true; + }, + ), + if (totalWorkingHours > 0.0) ...[ + 12.height, + WorkingTimeTile( + timerList: timerList, + totalWorkingTime: totalWorkingHours, + ), + ], + ], + ); + } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart new file mode 100644 index 00000000..20ab2451 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/create_system_internal_audit_form.dart @@ -0,0 +1,278 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/device/asset.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; +import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/parts/auto_complete_parts_field.dart'; +import '../../../../../../../new_views/common_widgets/default_app_bar.dart'; + +class CreateSystemInternalAuditForm extends StatefulWidget { + static const String id = "/create-system-audit-view"; + + const CreateSystemInternalAuditForm({Key? key}) : super(key: key); + + @override + _CreateSystemInternalAuditFormState createState() => _CreateSystemInternalAuditFormState(); +} +//TODO remove unnecessary code + +class _CreateSystemInternalAuditFormState extends State with TickerProviderStateMixin { + late TextEditingController _commentController; + late InternalAuditProvider _internalAuditProvider; + final SystemInternalAuditFormModel _model = SystemInternalAuditFormModel(); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + final List _attachments = []; + late TextEditingController _woAutoCompleteController; + bool showLoading = false; + + @override + void initState() { + super.initState(); + _commentController = TextEditingController(); + _woAutoCompleteController = TextEditingController(); + _internalAuditProvider = Provider.of(context, listen: false); + } + + @override + void dispose() { + _commentController.dispose(); + _woAutoCompleteController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: DefaultAppBar(title: 'System Internal Audit Checklist'.addTranslation), + body: Form( + key: _formKey, + child: Column( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: 'Wo Type'.addTranslation, + showShadow: false, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + initialValue: _model.workOrderType, + onSelect: (status) { + if (status != null) { + _model.workOrderType = status; + _model.workOrderDetailModel = SystemAuditWorkOrderDetailModel(); + _clearWorkOrderSelectionIds(); + _woAutoCompleteController.clear(); + setState(() {}); + } + }, + ), + 12.height, + SystemAuditWoAutoCompleteField( + woTypeId: _model.workOrderType?.value, + controller: _woAutoCompleteController, + clearAfterPick: false, + initialValue: '', + onPick: (wo) async { + InternalAuditProvider provider = Provider.of(context, listen: false); + setState(() { + showLoading = true; + }); + _model.workOrderDetailModel = await provider.loadAllWorkOrderDetailsByID(workOrderId: wo.id!, workOrderTypeId: _model.workOrderType!.value!); + updateWorkOrderReference(woTypeId: _model.workOrderType?.value, selectedId: wo.id); + + setState(() { + showLoading = false; + }); + }, + ), + 12.height, + // if(_internalAuditModel.woOrderNo!=null) + _workOrderInfoWidget(context), + 12.height, + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: 'Finding Type'.addTranslation, + showShadow: false, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + initialValue: _model.findingType, + onSelect: (status) { + if (status != null) { + _model.findingType = status; + setState(() {}); + } + }, + ), + 12.height, + AppTextFormField( + backgroundColor: AppColor.fieldBgColor(context), + labelText: 'Finding Description'.addTranslation, + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + alignLabelWithHint: true, + textInputType: TextInputType.multiline, + showShadow: false, + onSaved: (value) { + _model.findingDescription = value; + }, + ), + 16.height, + AttachmentPicker( + label: context.translation.attachments, + attachment: _attachments, + buttonColor: AppColor.primary10, + onlyImages: false, + buttonIcon: 'attachment_icon'.toSvgAsset(color: AppColor.primary10), + ), + ], + ).toShadowContainer(context), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: context.translation.submitRequest, + onPressed: _submit, + ), + ), + ], + ), + ), + ); + } + + Future _submit() async { + InternalAuditProvider internalAuditProvider = Provider.of(context, listen: false); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + _model.attachments=[]; + for (var item in _attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + _model.attachments?.add(InternalAuditAttachments(id: item.id, name: fileName)); + } + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + _model.auditorId = context.userProvider.user?.userID; + bool status = await internalAuditProvider.addSystemInternalAudit(context: context, request: _model); + Navigator.pop(context); + if (status) { + Navigator.pop(context); + } + } + } + + Widget _workOrderInfoWidget(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: showLoading ? Colors.white : const Color(0xffF4F6FC), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0xff212936).withOpacity(.03), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + _model.workOrderType?.name ?? 'WO Type', + style: TextStyle( + fontSize: 14.toScreenWidth, + fontWeight: FontWeight.w500, + fontStyle: FontStyle.normal, + color: Colors.black87, + decoration: TextDecoration.none, + ), + ).expanded, + const Icon( + Icons.info, + color: Color(0xff7D859A), + size: 20, + ), + ], + ), + 6.height, + "${context.translation.site}: ${_model.workOrderDetailModel?.woSite ?? '-'}".bodyText(context), + "${'Eng. Name'.addTranslation}: ${_model.workOrderDetailModel?.woAssignedEngineer ?? '-'}".bodyText(context), + "${'Asset Name'.addTranslation}: ${_model.workOrderDetailModel?.woAssetName ?? '-'}".bodyText(context), + "${context.translation.model}: ${_model.workOrderDetailModel?.woModel ?? '-'}".bodyText(context), + "${context.translation.manufacture}: ${_model.workOrderDetailModel?.woManufacturer ?? '-'}".bodyText(context), + "${'SN'.addTranslation}: ${_model.workOrderDetailModel?.wosn ?? '-'}".bodyText(context), + "${context.translation.assetNo}: ${_model.workOrderDetailModel?.woAssetNo ?? '-'}".bodyText(context), + ], + ), + ).toShimmer(isShow: showLoading, context: context); + } + + void updateWorkOrderReference({int? woTypeId, int? selectedId}) { + _clearWorkOrderSelectionIds(); + if (woTypeId == null || selectedId == null) return; + switch (woTypeId) { + case 1: // CM + _model.correctiveMaintenanceId = selectedId; + break; + case 2: // Gas Refill + _model.gasRefillId = selectedId; + break; + case 3: // Asset Transfer + _model.assetTransferId = selectedId; + break; + case 4: // PPM Request + _model.planPreventiveVisitId = selectedId; + break; + case 5: // Recurrent WOx + _model.planRecurrentTaskId = selectedId; + break; + case 6: // Task + _model.taskJobId = selectedId; + break; + case 7: // Recall & Alert + _model.taskAlertJobId = selectedId; + break; + } + } + + void _clearWorkOrderSelectionIds() { + _model.correctiveMaintenanceId = null; + _model.planPreventiveVisitId = null; + _model.assetTransferId = null; + _model.taskJobId = null; + _model.taskAlertJobId = null; + _model.gasRefillId = null; + _model.planRecurrentTaskId = null; + } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart new file mode 100644 index 00000000..9aa79453 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/system_audit_work_order_auto_complete_field.dart @@ -0,0 +1,149 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/app_style/app_text_style.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; + +class SystemAuditWoAutoCompleteField extends StatefulWidget { + final String initialValue; + final int? woTypeId; + final TextEditingController controller; + final bool clearAfterPick; + final Function(WoAutoCompleteModel) onPick; + + const SystemAuditWoAutoCompleteField({Key? key, required this.initialValue, this.woTypeId, required this.onPick, this.clearAfterPick = true, required this.controller}) : super(key: key); + + @override + _SystemAuditWoAutoCompleteFieldState createState() => _SystemAuditWoAutoCompleteFieldState(); +} + +class _SystemAuditWoAutoCompleteFieldState extends State { + late InternalAuditProvider _provider; + + // + // late TextEditingController _controller; + + bool loading = false; + + @override + void initState() { + // w controller = widget.controller ?? TextEditingController(text: widget.initialValue); + super.initState(); + _provider = Provider.of(context, listen: false); + } + + @override + void didUpdateWidget(covariant SystemAuditWoAutoCompleteField oldWidget) { + // if (widget.initialValue != oldWidget.initialValue) { + // // _controller = widget.controller ?? TextEditingController(text: widget.initialValue); + // } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + if (widget.controller == null) { + // _controller.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10)); + return Container( + decoration: BoxDecoration( + color: AppColor.background(context), + borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)), + // boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], + ), + child: Autocomplete( + optionsBuilder: (TextEditingValue textEditingValue) async { + if (textEditingValue.text.isEmpty) { + if (loading) { + setState(() { + loading = false; + }); + } + return const Iterable.empty(); + } + if (!loading) { + setState(() { + loading = true; + }); + } + List workOrders = (await _provider.getWorkOrderByWoType(text: textEditingValue.text, woId:widget.woTypeId)); + setState(() { + loading = false; + }); + return workOrders; + }, + displayStringForOption: (WoAutoCompleteModel option) => option.displayName ?? '', + fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { + return TextField( + controller: widget.controller, + focusNode: fieldFocusNode, + style: AppTextStyles.bodyText.copyWith(color: AppColor.black10), + textAlign: TextAlign.start, + decoration: InputDecoration( + border: border, + disabledBorder: border, + focusedBorder: border, + enabledBorder: border, + errorBorder: border, + contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth), + constraints: const BoxConstraints(), + suffixIconConstraints: const BoxConstraints(maxHeight: 24, maxWidth: 24 + 8), + filled: true, + fillColor: AppColor.fieldBgColor(context), + errorStyle: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60), + floatingLabelStyle: AppTextStyle.body1.copyWith(fontWeight: FontWeight.w500, color: context.isDark ? null : AppColor.neutral20), + labelText: context.translation.woNumber, + labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)), + suffixIcon: loading ? const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3.0).paddingOnly(end: 8) : null, + ), + textInputAction: TextInputAction.search, + onChanged: (text) { + fieldTextEditingController.text = text; + }, + onSubmitted: (String value) { + onFieldSubmitted(); + }, + ); + }, + onSelected: (WoAutoCompleteModel selection) { + if (widget.clearAfterPick) { + widget.controller.clear(); + } else { + widget.controller.text = (selection.displayName ?? ""); + } + widget.onPick(selection); + }, + ), + ); + } + + // String getUrl(int? woTypeId) { + // if (woTypeId == null) return ''; + // String base = URLs.woAutoCompleteBase; + // var urlMap = { + // 1: '$base/ServiceRequest/WorkOrderAutoComplete', // CM + // 2: '$base/GasRefill/GetGasRefillAutoComplete', // Gas Refill + // 3: '$base/AssetTransfer/GetAssetTransferAutoComplete', // Asset Transfer + // 4: '$base/PlanPreventiveVisit/GetAutoCompletePlanPreventiveVisit', // PPM Request + // 5: '$base/PlanRecurrentTasks/GetPlanRecurrentTask', // Recurrent WO + // 6: '$base/TaskJobs/AutocompleteTaskJob', // Task + // 7: '$base/TaskJobs/AutocompleteTaskJob', // Recall & Alert + // }; + // return urlMap[woTypeId] ?? ''; + // } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart new file mode 100644 index 00000000..484d5b4e --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart @@ -0,0 +1,219 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; + +import 'package:test_sa/views/widgets/loaders/app_loading.dart'; + +class SystemInternalAuditDetailPage extends StatefulWidget { + static const String id = "/details-system-internal-audit"; + final int auditId; + + SystemInternalAuditDetailPage({Key? key, required this.auditId}) : super(key: key); + + @override + _SystemInternalAuditDetailPageState createState() { + return _SystemInternalAuditDetailPageState(); + } +} + +class _SystemInternalAuditDetailPageState extends State { + bool isWoType = true; + SystemInternalAuditDataModel? model; + late InternalAuditProvider _internalAuditProvider; + List allAttachments = []; + + @override + void initState() { + super.initState(); + _internalAuditProvider = Provider.of(context, listen: false); + WidgetsBinding.instance.addPostFrameCallback((_) { + getAuditData(); + }); + } + + Future getAuditData() async { + model = await _internalAuditProvider.getInternalSystemAuditById(widget.auditId); + allAttachments.clear(); + allAttachments = [ + ...(model?.attachments ?? []), + ...(model?.engineerData?.attachments ?? []), + ]; + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + //TODO need to check when implementing provider needed or not . + return Scaffold( + appBar: const DefaultAppBar(title: "Request Details"), + body: Selector( + selector: (_, provider) => provider.isLoading, + builder: (_, isLoading, __) { + if (isLoading) return const ALoading(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + assetInformation(), + 8.height, + workOrderInformation(), + 8.height, + //TODO need to check for comments + if (model?.findingDescription?.isNotEmpty ?? false) ...[ + const Divider().defaultStyle(context), + // Text( + // "Comments".addTranslation, + // style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + // ), + model!.findingDescription!.bodyText(context), + ], + //TODO need to check for attachments + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 8.height, + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.name ?? '') ?? '').toList() ?? []), + ], + ], + ).paddingAll(0).toShadowContainer(context), + ).expanded, + if (context.userProvider.isEngineer) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Update", + onPressed: () async { + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => UpdateSystemInternalAuditPage(model: model), + ), + ); + if (result == true) { + await getAuditData(); + setState(() {}); // refresh UI with new model + } + }), + ), + ], + ); + }, + )); + } + + Widget workOrderInformation() { + final details = model?.workOrderDetails; + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text( + "WO Info", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + labelValueText(context, context.translation.woNumber, details?.workOrderNo), + labelValueText(context, 'WO Type'.addTranslation, model?.workOrderType?.name), + labelValueText(context, context.translation.site, details?.woSite), + labelValueText(context, context.translation.assetName, details?.woAssetName), + labelValueText(context, context.translation.manufacture, details?.woManufacturer), + labelValueText(context, context.translation.model, details?.woModel), + ], + ); + } + + Widget requestDetails() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text( + "Request Details", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + checklistWidget(value: 'Asset Tag'.addTranslation), + checklistWidget(value: 'Expired PM Tag'.addTranslation), + ], + ); + } + + Widget assetInformation() { + final details = model?.workOrderDetails; + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Asset Details", + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 6.height, + labelValueText(context, context.translation.assetName, details?.woAssetName), + labelValueText(context, context.translation.assetNo, details?.woAssetNo), + labelValueText(context, context.translation.manufacture, details?.woManufacturer), + labelValueText(context, context.translation.model, details?.woModel), + ], + ); + } + + Widget labelValueText(BuildContext context, String label, String? value) { + if (value == null || value.isEmpty) return const SizedBox.shrink(); + + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Text( + '$label: $value', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral120, + ), + ), + ); + } + + Widget checklistWidget({required String value}) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: true, + activeColor: AppColor.neutral120, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: const VisualDensity(horizontal: -4, vertical: -3), + onChanged: (value) {}, + ), + value.bodyText(context), + ], + ); + } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart new file mode 100644 index 00000000..0d32a4a8 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart @@ -0,0 +1,117 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/all_requests_and_count_model.dart'; +import 'package:test_sa/models/new_models/dashboard_detail.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_detail_page.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +class SystemInternalAuditItemView extends StatelessWidget { + final Data? requestData; + final RequestsDetails? requestDetails; + final bool showShadow; + + const SystemInternalAuditItemView({Key? key, this.requestData, this.requestDetails, this.showShadow = true}) : super(key: key); + + @override + Widget build(BuildContext context) { + //TODO need to refactor this code repetation @waseem + if (requestData != null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: requestData!.statusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, requestData!.statusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, requestData!.statusName!), + ), + 1.width.expanded, + Text( + requestData!.transactionDate?.toServiceRequestCardFormat ?? "", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 8.height, + (requestData?.typeTransaction ?? "Internal Audit Request").heading5(context), + infoWidget(label: context.translation.assetNo, value: requestData?.assetNumber, context: context), + + 8.height, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.translation.viewDetails, + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)), + ), + 4.width, + Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14) + ], + ), + ], + ).toShadowContainer(context, withShadow: showShadow).onPress(() async { + Navigator.push(context, MaterialPageRoute(builder: (context) => SystemInternalAuditDetailPage(auditId: requestDetails!.id!))); + }); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: requestDetails!.status!, + textColor: AppColor.getRequestStatusTextColorByName(context, requestDetails?.status!), + backgroundColor: AppColor.getRequestStatusColorByName(context, requestDetails?.status!), + ), + 1.width.expanded, + Text( + requestDetails!.date?.toServiceRequestCardFormat ?? "", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 8.height, + (requestDetails?.nameOfType ?? "Internal Audit Request").heading5(context), + infoWidget(label: context.translation.assetNumber, value: requestDetails!.assetNo, context: context), + infoWidget(label: context.translation.assetSN, value: requestDetails!.assetSN, context: context), + infoWidget(label: context.translation.model, value: requestDetails!.model, context: context), + 8.height, + // infoWidget(label: context.translation.site, value: requestDetails!.site, context: context), + 8.height, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.translation.viewDetails, + style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context)), + ), + 4.width, + Icon(Icons.arrow_forward, color: AppColor.blueStatus(context), size: 14) + ], + ), + ], + ).toShadowContainer(context, withShadow: showShadow).onPress(() async { + Navigator.push(context, MaterialPageRoute(builder: (context) => SystemInternalAuditDetailPage(auditId: requestDetails!.id!))); + }); + } + + Widget infoWidget({required String label, String? value, required BuildContext context}) { + if (value != null && value.isNotEmpty) { + return '$label: $value'.bodyText(context); + } + return const SizedBox(); + } +} diff --git a/lib/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart b/lib/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart new file mode 100644 index 00000000..1d50b7b8 --- /dev/null +++ b/lib/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart @@ -0,0 +1,316 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/timer_model.dart'; +import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_timer_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; +import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/new_views/common_widgets/working_time_tile.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; +import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart'; + +class UpdateSystemInternalAuditPage extends StatefulWidget { + static const String id = "update-system-internal-audit"; + SystemInternalAuditDataModel? model; + + UpdateSystemInternalAuditPage({Key? key, this.model}) : super(key: key); + + @override + State createState() => _UpdateSystemInternalAuditPageState(); +} + +class _UpdateSystemInternalAuditPageState extends State { + final bool _isLoading = false; + double totalWorkingHours = 0.0; + AuditFormModel formModel = AuditFormModel(); + final TextEditingController _workingHoursController = TextEditingController(); + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + List _attachments = []; + + //TODO need to check if it's needed or not.. + List timerList = []; + + @override + void initState() { + populateForm(); + super.initState(); + } + + void populateForm() { + formModel.requestId = widget.model?.id; + formModel.id = widget.model?.id; + _attachments = widget.model?.engineerData?.attachments?.map((e) => GenericAttachmentModel(id: e.id?.toInt() ?? 0, name: e.name!)).toList() ?? []; + + calculateWorkingTime(); + } + + void calculateWorkingTime() { + final helperModel = formModel; + final timers = helperModel.auditTimers ?? []; + totalWorkingHours = timers.fold(0.0, (sum, item) { + if (item.startDate == null || item.endDate == null) return sum; + try { + final start = DateTime.parse(item.startDate!); + final end = DateTime.parse(item.endDate!); + final diffInHours = end.difference(start).inSeconds / 3600.0; // convert to hours + return sum + diffInHours; + } catch (_) { + return sum; + } + }); + timerList = timers.map((e) { + return TimerHistoryModel( + id: e.id, + startTime: e.startDate, + endTime: e.endDate, + workingHours: e.startDate, + ); + }).toList(); + } + + _onSubmit(BuildContext context) async { + bool isTimerPickerEnable = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; + InternalAuditProvider provider = Provider.of(context, listen: false); + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + + formModel.auditTimers ??= []; + if (formModel.auditTimePicker != null) { + int durationInSecond = formModel.auditTimePicker!.endAt!.difference(formModel.auditTimePicker!.startAt!).inSeconds; + formModel.auditTimers?.add( + InternalAuditTimerModel( + id: 0, + startDate: formModel.auditTimePicker!.startAt!.toIso8601String(), // Handle potential null + endDate: formModel.auditTimePicker!.endAt?.toIso8601String(), // Handle potential null + totalWorkingHour: ((durationInSecond) / 60 / 60), + ), + ); + } + formModel.timerModelList?.forEach((timer) { + int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; + formModel.auditTimers?.add( + InternalAuditTimerModel( + id: 0, + startDate: timer.startAt!.toIso8601String(), // Handle potential null + endDate: timer.endAt?.toIso8601String(), // Handle potential null + totalWorkingHour: ((durationInSecond) / 60 / 60), + ), + ); + }); + + _formKey.currentState!.save(); + formModel.attachments = []; + for (var item in _attachments) { + String fileName = ServiceRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name?.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + formModel.attachments!.add( + InternalAuditAttachments( + id: item.id, + // originalName: fileName, + name: fileName, + ), + ); + } + log('submit press'); + log('data ${formModel.toJson()}'); + final success = await provider.updateSystemInternalAudit(model: formModel); + Navigator.pop(context); + if (formModel.isComplete == true) { + //Navigate to List screen. + } else if (formModel.isComplete == false) { + if (success) { + Navigator.of(context).pop(true); + } else { + Fluttertoast.showToast(msg: 'Failed to update'); + } + } + } + + @override + void dispose() { + _workingHoursController.dispose(); + super.dispose(); + } + + void updateTimer({TimerModel? timer}) { + if (timer?.startAt != null && timer?.endAt != null) { + final start = timer!.startAt!; + final end = timer.endAt!; + + final difference = end.difference(start); + final totalHours = difference.inSeconds / 3600.0; + formModel.startTime = start; + formModel.endTime = end; + formModel.totalHours = totalHours; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: DefaultAppBar( + title: 'Update Information'.addTranslation, + onWillPopScope: () { + formModel.isComplete = false; + _onSubmit(context); + }, + ), + key: _scaffoldKey, + body: Form( + key: _formKey, + child: LoadingManager( + isLoading: _isLoading, + isFailedLoading: false, + stateCode: 200, + onRefresh: () async {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + 8.height, + AppTextFormField( + labelText: 'Debrief'.addTranslation, + textInputType: TextInputType.multiline, + hintStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10), + labelStyle: TextStyle(color: context.isDark ? AppColor.white10 : AppColor.black10), + alignLabelWithHint: true, + initialValue: formModel.debrief, + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + onSaved: (value) { + formModel.debrief = value; + }, + ), + 8.height, + _timerWidget(context, totalWorkingHours), + 16.height, + AttachmentPicker( + label: 'Upload Attachment', + attachment: _attachments, + buttonColor: AppColor.primary10, + onlyImages: false, + buttonIcon: 'attachment_icon'.toSvgAsset( + color: AppColor.primary10, + ), + ), + 8.height, + ], + ).toShadowContainer(context), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + AppFilledButton( + label: context.translation.save, + buttonColor: context.isDark ? AppColor.neutral70 : AppColor.white60, + textColor: context.isDark ? AppColor.white10 : AppColor.black10, + onPressed: () { + log('button press '); + formModel.isComplete = false; + _onSubmit(context); + }).expanded, + 12.width, + AppFilledButton( + label: context.translation.complete, + buttonColor: AppColor.primary10, + onPressed: () { + formModel.isComplete = true; + _onSubmit(context); + }).expanded, + ], + ), + ), + ], + )), + ), + ).handlePopScope( + cxt: context, + onSave: () { + formModel.isComplete = false; + _onSubmit(context); + }); + } + + Widget _timerWidget(BuildContext context, double totalWorkingHours) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ///this is for multiple timers... + // AppTimer( + // label: context.translation.timer, + // timer: formModel.auditTimerModel, + // // enabled: enableTimer, + // pickerTimer: formModel.auditTimePicker, + // pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''), + // onPick: (time) { + // formModel.auditTimePicker = time; + // }, + // timerProgress: (isRunning) {}, + // onChange: (timer) async { + // formModel.auditTimerModel = timer; + // if (timer.startAt != null && timer.endAt != null) { + // formModel.timerModelList = formModel.timerModelList ?? []; + // formModel.timerModelList!.add(timer); + // } + // return true; + // }, + // ), + AppTimer( + label: context.translation.workingHours, + showPicker: false, + timer: formModel.auditTimerModel, + pickerFromDate: DateTime.tryParse(widget.model?.createdDate ?? ''), + pickerTimer: formModel.auditTimePicker, + onPick: (time) { + updateTimer(timer: time); + }, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(10), + ), + timerProgress: (isRunning) {}, + onChange: (timer) async { + updateTimer(timer: timer); + log('here onChange ${timer.startAt}'); + + return true; + }, + ), + if (totalWorkingHours > 0.0) ...[ + 12.height, + WorkingTimeTile( + timerList: timerList, + totalWorkingTime: totalWorkingHours, + ), + ], + ], + ); + } +} diff --git a/lib/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart b/lib/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart new file mode 100644 index 00000000..e4454551 --- /dev/null +++ b/lib/modules/internal_audit_module/provider/internal_audit_checklist_provider.dart @@ -0,0 +1,36 @@ + +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +class InternalAuditCheckListProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading ?? false) return -2; + loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.getInternalAuditChecklist); + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List listJson = json.decode(response.body)["data"]; + items = listJson.map((department) => Lookup.fromJson(department)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } +} + diff --git a/lib/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart b/lib/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart new file mode 100644 index 00000000..9371cb1d --- /dev/null +++ b/lib/modules/internal_audit_module/provider/internal_audit_finding_type_provider.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +class InternalAuditFindingTypeProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading ?? false) return -2; + loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.getInternalAuditFindingType); + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List listJson = json.decode(response.body)["data"]; + items = listJson.map((department) => Lookup.fromJson(department)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } +} diff --git a/lib/modules/internal_audit_module/provider/internal_audit_provider.dart b/lib/modules/internal_audit_module/provider/internal_audit_provider.dart new file mode 100644 index 00000000..75f0a436 --- /dev/null +++ b/lib/modules/internal_audit_module/provider/internal_audit_provider.dart @@ -0,0 +1,211 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/models/device/asset_search.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/models/new_models/asset_nd_auto_complete_by_dynamic_codes_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/update_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/equipment_internal_audit_form_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart'; +import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_form_model.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; + +class InternalAuditProvider extends ChangeNotifier { + final pageItemNumber = 10; + final searchPageItemNumber = 10; + int pageNo = 1; + + void reset() { + pageNo = 1; + stateCode = null; + } + + int? stateCode; + bool isDetailLoading = false; + bool nextPage = false; + bool isNextPageLoading = false; + bool isLoading = false; + + Future getEquipmentInternalAuditById(int id) async { + try { + isLoading = true; + notifyListeners(); + Response response = await ApiManager.instance.get( + "${URLs.getInternalAuditEquipmentById}?internalAuditEquipmentId=$id", + ); + if (response.statusCode >= 200 && response.statusCode < 300) { + final decodedBody = jsonDecode(response.body); + EquipmentInternalAuditDataModel model = EquipmentInternalAuditDataModel.fromJson(decodedBody["data"]); + isLoading = false; + notifyListeners(); + return model; + } else { + isLoading = false; + notifyListeners(); + return null; + } + } catch (error) { + isLoading = false; + notifyListeners(); + return null; + } + } + + Future getInternalSystemAuditById(int id) async { + try { + isLoading = true; + notifyListeners(); + Response response = await ApiManager.instance.get("${URLs.getInternalAuditSystemById}?AuditSystemId=$id"); + if (response.statusCode >= 200 && response.statusCode < 300) { + final decodedBody = jsonDecode(response.body); + SystemInternalAuditDataModel model = SystemInternalAuditDataModel.fromJson(decodedBody["data"]); + isLoading = false; + notifyListeners(); + return model; + } else { + isLoading = false; + notifyListeners(); + return null; + } + } catch (error) { + isLoading = false; + notifyListeners(); + return null; + } + } + + Future loadAllWorkOrderDetailsByID({required int workOrderTypeId, required int workOrderId}) async { + try { + isLoading = true; + notifyListeners(); + Response response = await ApiManager.instance.get("${URLs.loadAllWorkOrderDetailsByID}?workOrderTypeId=$workOrderTypeId&workOrderId=$workOrderId"); + if (response.statusCode >= 200 && response.statusCode < 300) { + final decodedBody = jsonDecode(response.body); + SystemAuditWorkOrderDetailModel model = SystemAuditWorkOrderDetailModel.fromJson(decodedBody["data"]); + isLoading = false; + notifyListeners(); + return model; + } else { + isLoading = false; + notifyListeners(); + return null; + } + } catch (error) { + isLoading = false; + notifyListeners(); + return null; + } + } + + Future updateEquipmentInternalAudit({required AuditFormModel model}) async { + isLoading = true; + Response response; + try { + response = await ApiManager.instance.put(URLs.updateAuditEquipmentsEngineer, body: model.toJson()); + stateCode = response.statusCode; + isLoading = false; + notifyListeners(); + if (stateCode == 200) { + return true; + } + return false; + } catch (error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return false; + } + } + + Future updateSystemInternalAudit({required AuditFormModel model}) async { + isLoading = true; + Response response; + try { + response = await ApiManager.instance.put(URLs.updateAuditSystemEngineer, body: model.toJson()); + stateCode = response.statusCode; + isLoading = false; + notifyListeners(); + log('status code ${stateCode}'); + if (stateCode == 200) { + return true; + } + return false; + } catch (error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return false; + } + } + + Future addEquipmentInternalAudit({ + required BuildContext context, + required EquipmentInternalAuditFormModel request, + }) async { + bool status = false; + Response response; + try { + response = await ApiManager.instance.post(URLs.addOrUpdateEquipmentInternalAudit, body: request.toJson()); + if (response.statusCode >= 200 && response.statusCode < 300) { + status = true; + notifyListeners(); + } else { + Fluttertoast.showToast(msg: "${context.translation.failedRequestMessage} :${json.decode(response.body)['message']}"); + status = false; + } + return status; + } catch (error) { + print(error); + status = false; + notifyListeners(); + return status; + } + } + + Future addSystemInternalAudit({ + required BuildContext context, + required SystemInternalAuditFormModel request, + }) async { + bool status = false; + Response response; + try { + response = await ApiManager.instance.post(URLs.addOrUpdateInternalAuditSystem, body: request.toJson()); + if (response.statusCode >= 200 && response.statusCode < 300) { + status = true; + notifyListeners(); + } else { + Fluttertoast.showToast(msg: "${context.translation.failedRequestMessage} :${json.decode(response.body)['message']}"); + status = false; + } + return status; + } catch (error) { + print(error); + status = false; + notifyListeners(); + return status; + } + } + + Future> getWorkOrderByWoType({String? text, required int? woId}) async { + late Response response; + try { + Response response = await ApiManager.instance.get("${URLs.getWoAutoComplete}?workOrderTypeId=$woId&search=$text"); + // response = await ApiManager.instance.post(url, body: {"search": text}); + List woOrderList = []; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + woOrderList = categoriesListJson.map((wo) => WoAutoCompleteModel.fromJson(wo)).toList(); + } + return woOrderList; + } catch (error) { + return []; + } + } +} diff --git a/lib/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart b/lib/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart new file mode 100644 index 00000000..7c7b4e5e --- /dev/null +++ b/lib/modules/internal_audit_module/provider/internal_audit_wo_type_provider.dart @@ -0,0 +1,35 @@ + +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +class InternalAuditWoTypeProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading ?? false) return -2; + loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.getInternalAuditWoType); + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List listJson = json.decode(response.body)["data"]; + items = listJson.map((department) => Lookup.fromJson(department)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } +} + diff --git a/lib/modules/tm_module/tasks_wo/update_task_request_view.dart b/lib/modules/tm_module/tasks_wo/update_task_request_view.dart index d25049df..40871d70 100644 --- a/lib/modules/tm_module/tasks_wo/update_task_request_view.dart +++ b/lib/modules/tm_module/tasks_wo/update_task_request_view.dart @@ -271,7 +271,7 @@ class _UpdateTaskRequestState extends State { taskModel?.timerModelList?.forEach((timer) { int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; taskModel.taskJobActivityEngineerTimers?.add( - TaskJobActivityEngineerTimer( + TaskJobActivityEngineerTimer ( id: 0, startDate: timer.startAt!.toIso8601String(), endDate: timer.endAt?.toIso8601String(), totalWorkingHour: ((durationInSecond) / 60 / 60), comment: timer.comments ?? comments), ); }); diff --git a/lib/modules/traf_module/asset_analytics_model.dart b/lib/modules/traf_module/asset_analytics_model.dart new file mode 100644 index 00000000..2f265c1b --- /dev/null +++ b/lib/modules/traf_module/asset_analytics_model.dart @@ -0,0 +1,172 @@ +class AssetAnalytics { + num? assetId; + String? assetNumber; + String? assetName; + num? lastPrice; + String? lastPONo; + String? lastPODate; + num? sameAssetQuantity; + String? slaStatus; + String? contractNumber; + String? contractStartDate; + String? contractEndDate; + num? assetAgeInDays; + String? assetAgeDescription; + num? totalMaintenanceCost; + num? sparePartCost; + num? laborCost; + num? exchangeCost; + num? travelCost; + num? netBookValue; + num? purchasedPrice; + num? lifeSpan; + num? mtbf; + String? mtbfDescription; + num? totalCMWorkOrders; + num? downTimeHours; + String? downTimeDescription; + num? upTimeHours; + String? upTimeDescription; + num? operationHours; + String? installationDate; + String? modelName; + String? manufacturerName; + String? serialNumber; + String? departmentName; + String? siteName; + num? id; + String? createdBy; + String? createdDate; + String? modifiedBy; + String? modifiedDate; + + AssetAnalytics( + {this.assetId, + this.assetNumber, + this.assetName, + this.lastPrice, + this.lastPONo, + this.lastPODate, + this.sameAssetQuantity, + this.slaStatus, + this.contractNumber, + this.contractStartDate, + this.contractEndDate, + this.assetAgeInDays, + this.assetAgeDescription, + this.totalMaintenanceCost, + this.sparePartCost, + this.laborCost, + this.exchangeCost, + this.travelCost, + this.netBookValue, + this.purchasedPrice, + this.lifeSpan, + this.mtbf, + this.mtbfDescription, + this.totalCMWorkOrders, + this.downTimeHours, + this.downTimeDescription, + this.upTimeHours, + this.upTimeDescription, + this.operationHours, + this.installationDate, + this.modelName, + this.manufacturerName, + this.serialNumber, + this.departmentName, + this.siteName, + this.id, + this.createdBy, + this.createdDate, + this.modifiedBy, + this.modifiedDate}); + + AssetAnalytics.fromJson(Map json) { + assetId = json['assetId']; + assetNumber = json['assetNumber']; + assetName = json['assetName']; + lastPrice = json['lastPrice']; + lastPONo = json['lastPONo']; + lastPODate = json['lastPODate']; + sameAssetQuantity = json['sameAssetQuantity']; + slaStatus = json['slaStatus']; + contractNumber = json['contractNumber']; + contractStartDate = json['contractStartDate']; + contractEndDate = json['contractEndDate']; + assetAgeInDays = json['assetAgeInDays']; + assetAgeDescription = json['assetAgeDescription']; + totalMaintenanceCost = json['totalMaintenanceCost']; + sparePartCost = json['sparePartCost']; + laborCost = json['laborCost']; + exchangeCost = json['exchangeCost']; + travelCost = json['travelCost']; + netBookValue = json['netBookValue']; + purchasedPrice = json['purchasedPrice']; + lifeSpan = json['lifeSpan']; + mtbf = json['mtbf']; + mtbfDescription = json['mtbfDescription']; + totalCMWorkOrders = json['totalCMWorkOrders']; + downTimeHours = json['downTimeHours']; + downTimeDescription = json['downTimeDescription']; + upTimeHours = json['upTimeHours']; + upTimeDescription = json['upTimeDescription']; + operationHours = json['operationHours']; + installationDate = json['installationDate']; + modelName = json['modelName']; + manufacturerName = json['manufacturerName']; + serialNumber = json['serialNumber']; + departmentName = json['departmentName']; + siteName = json['siteName']; + id = json['id']; + createdBy = json['createdBy']; + createdDate = json['createdDate']; + modifiedBy = json['modifiedBy']; + modifiedDate = json['modifiedDate']; + } + + Map toJson() { + final Map data = new Map(); + data['assetId'] = this.assetId; + data['assetNumber'] = this.assetNumber; + data['assetName'] = this.assetName; + data['lastPrice'] = this.lastPrice; + data['lastPONo'] = this.lastPONo; + data['lastPODate'] = this.lastPODate; + data['sameAssetQuantity'] = this.sameAssetQuantity; + data['slaStatus'] = this.slaStatus; + data['contractNumber'] = this.contractNumber; + data['contractStartDate'] = this.contractStartDate; + data['contractEndDate'] = this.contractEndDate; + data['assetAgeInDays'] = this.assetAgeInDays; + data['assetAgeDescription'] = this.assetAgeDescription; + data['totalMaintenanceCost'] = this.totalMaintenanceCost; + data['sparePartCost'] = this.sparePartCost; + data['laborCost'] = this.laborCost; + data['exchangeCost'] = this.exchangeCost; + data['travelCost'] = this.travelCost; + data['netBookValue'] = this.netBookValue; + data['purchasedPrice'] = this.purchasedPrice; + data['lifeSpan'] = this.lifeSpan; + data['mtbf'] = this.mtbf; + data['mtbfDescription'] = this.mtbfDescription; + data['totalCMWorkOrders'] = this.totalCMWorkOrders; + data['downTimeHours'] = this.downTimeHours; + data['downTimeDescription'] = this.downTimeDescription; + data['upTimeHours'] = this.upTimeHours; + data['upTimeDescription'] = this.upTimeDescription; + data['operationHours'] = this.operationHours; + data['installationDate'] = this.installationDate; + data['modelName'] = this.modelName; + data['manufacturerName'] = this.manufacturerName; + data['serialNumber'] = this.serialNumber; + data['departmentName'] = this.departmentName; + data['siteName'] = this.siteName; + data['id'] = this.id; + data['createdBy'] = this.createdBy; + data['createdDate'] = this.createdDate; + data['modifiedBy'] = this.modifiedBy; + data['modifiedDate'] = this.modifiedDate; + return data; + } +} diff --git a/lib/modules/traf_module/asset_auto_complete_field.dart b/lib/modules/traf_module/asset_auto_complete_field.dart index 0abb378b..e215c4a7 100644 --- a/lib/modules/traf_module/asset_auto_complete_field.dart +++ b/lib/modules/traf_module/asset_auto_complete_field.dart @@ -105,7 +105,7 @@ class _AssetAutoCompleteFieldState extends State { fillColor: AppColor.fieldBgColor(context), errorStyle: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60), floatingLabelStyle: AppTextStyle.body1.copyWith(fontWeight: FontWeight.w500, color: context.isDark ? null : AppColor.neutral20), - labelText: context.translation.oracleCode, + labelText: "Name/Oracle Code", labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)), suffixIcon: loading ? const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3.0).paddingOnly(end: 8) : null, ), diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 69756285..66133805 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -165,6 +165,10 @@ class _CreateTRAFRequestPageState extends State { AppTextFormField( initialValue: "", labelText: "How would the requested technology solve the current situation and/or serve the purpose?", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), @@ -178,6 +182,10 @@ class _CreateTRAFRequestPageState extends State { AppTextFormField( initialValue: "", labelText: "What is the current practice?", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, @@ -191,6 +199,10 @@ class _CreateTRAFRequestPageState extends State { initialValue: "", makeMultiLinesNull: true, labelText: "Census Q1", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, @@ -204,6 +216,10 @@ class _CreateTRAFRequestPageState extends State { initialValue: "", makeMultiLinesNull: true, labelText: "Census Q2", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, @@ -217,6 +233,10 @@ class _CreateTRAFRequestPageState extends State { initialValue: "", makeMultiLinesNull: true, labelText: "Census Q3", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, @@ -230,6 +250,10 @@ class _CreateTRAFRequestPageState extends State { initialValue: "", makeMultiLinesNull: true, labelText: "Census Q4", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, @@ -264,7 +288,8 @@ class _CreateTRAFRequestPageState extends State { dense: true, contentPadding: EdgeInsets.zero, title: Text("Name: ${userBasedOnSearch[index].userName ?? ""}"), - subtitle: Text("Email: ${userBasedOnSearch[index].email ?? ""}\nPhone: ${userBasedOnSearch[index].phoneNumber ?? ""}"), + subtitle: Text( + "Position: ${userBasedOnSearch[index].position ?? "-"}\nEmail: ${userBasedOnSearch[index].email ?? ""}\nPhone: ${userBasedOnSearch[index].phoneNumber ?? ""}"), trailing: const Icon(Icons.delete_rounded, color: Color(0xffF63939), size: 20).onPress(() { userBasedOnSearch.removeAt(index); setState(() {}); @@ -286,7 +311,7 @@ class _CreateTRAFRequestPageState extends State { initialValue: isUsedSolelyOrShared, onSelect: (value) { isUsedSolelyOrShared = value; - trafRequest?.usingSolelyOrSharedId = value?.value; + trafRequest?.usingSolelyOrSharedId = value?.id; if (isUsedSolelyOrShared?.value != 1) { departments = []; trafRequest?.trafDepartments = []; @@ -329,7 +354,7 @@ class _CreateTRAFRequestPageState extends State { initialValue: otherServicesEffects, onSelect: (value) { otherServicesEffects = value; - trafRequest?.isEffectedId = value?.value; + trafRequest?.isEffectedId = value?.id; if (otherServicesEffects?.value != 1) { trafRequest?.effectedServices = null; FocusScope.of(context).unfocus(); @@ -347,6 +372,10 @@ class _CreateTRAFRequestPageState extends State { textInputType: TextInputType.multiline, alignLabelWithHint: true, node: otherServicesEffectsNode, + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, labelText: "List down these services and stat how would it be effected", backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), @@ -367,7 +396,7 @@ class _CreateTRAFRequestPageState extends State { initialValue: useInCombination, onSelect: (value) { useInCombination = value; - trafRequest?.isCombinationId = value?.value; + trafRequest?.isCombinationId = value?.id; if (useInCombination?.value != 1) { trafRequest?.usedWithCombination = null; FocusScope.of(context).unfocus(); @@ -384,6 +413,10 @@ class _CreateTRAFRequestPageState extends State { textInputType: TextInputType.multiline, alignLabelWithHint: true, node: useInCombinationNode, + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, labelText: "kindly describe in detail", backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), @@ -430,11 +463,46 @@ class _CreateTRAFRequestPageState extends State { } void _verifyAndSubmit() async { + if (requestType == null) { + "Select request type".showToast; + return; + } else if (requestType?.value == 1 && trafRequest?.assetNDId == null) { + "Oracle Code missing".showToast; + return; + } else if (requestType?.value == 2 && trafRequest?.assetId == null) { + "Pick asset".showToast; + return; + } + if (!_formKey.currentState!.validate()) return; + trafRequest?.employeeId = context.userProvider.user?.userID; trafRequest?.siteId = context.userProvider.user?.clientId; trafRequest?.trafContacts = []; trafRequest?.trafContacts = userBasedOnSearch.map((item) => TrafContacts(id: 0, trafId: 0, name: item.userName, userId: item.userId)).toList(); + if ((trafRequest?.trafContacts ?? []).isEmpty) { + "Please specify users going to use technology".showToast; + return; + } + if (isUsedSolelyOrShared == null) { + "Please specify technology solely or shared".showToast; + return; + } + if (isUsedSolelyOrShared?.value == 1 && (trafRequest?.trafDepartments ?? []).isEmpty) { + "Please specify departments and relations".showToast; + return; + } + + if (otherServicesEffects == null) { + "Please specify other services will be effected or not".showToast; + return; + } + + if (useInCombination == null) { + "Please specify equipment used in combination or not".showToast; + return; + } + Utils.showLoading(context); bool isSuccess = await Provider.of(context, listen: false).addTraf(trafRequest!.toJson()); Utils.hideLoading(context); diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index c69073c7..00f25512 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -15,6 +15,7 @@ import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; +import 'asset_analytics_model.dart'; import 'traf_request_model.dart'; class TrafRequestDetailPage extends StatefulWidget { @@ -93,14 +94,14 @@ class _TrafRequestDetailPageState extends State { ), 8.height, '${context.translation.employeeId}: ${data.employeeIdForDisplay ?? '-'}'.bodyText(context), - '${context.translation.name}: ${data.employeeName ?? '-'}'.bodyText(context), // todo ask shaheer - '${context.translation.email}: ${data.employeeEmail ?? '-'}'.bodyText(context), // todo ask shaheer - 'Position: ${data.positionName ?? '-'}'.bodyText(context), // todo ask shaheer + '${context.translation.name}: ${data.employeeName ?? '-'}'.bodyText(context), + '${context.translation.email}: ${data.employeeEmail ?? '-'}'.bodyText(context), + 'Position: ${data.positionName ?? '-'}'.bodyText(context), '${context.translation.site}: ${data.siteName ?? '-'}'.bodyText(context), - '${context.translation.department}: ${data.departments?.map((item) => item.departmentName).toList() ?? '-'}'.bodyText(context), - 'Requester Extension: ${data.requesterExtensionName ?? '-'}'.bodyText(context), - '${context.translation.extensionNo}: ${data.requesterExtensionNumber ?? '-'}'.bodyText(context), - '${context.translation.phoneNumber}: ${data.requesterContactNumber ?? '-'}'.bodyText(context), + '${context.translation.department}: ${data.departmentName}'.bodyText(context), + // 'Requester Extension: ${data.requesterExtensionName ?? '-'}'.bodyText(context), + 'Number/Contact Information: ${data.requesterContactNumber ?? '-'}'.bodyText(context), + 'Extension: ${data.requesterExtensionNumber ?? '-'}'.bodyText(context), ], ).toShadowContainer(context, borderRadius: 20, padding: 12); } @@ -131,10 +132,22 @@ class _TrafRequestDetailPageState extends State { .bodyText(context), 4.height, 'Is the requesting department going to use the technology solely or shared with other departments?:\n${data.usingSolelyOrSharedName ?? '-'}'.bodyText(context), + if ((data.departments ?? []).isNotEmpty) ...[ + 4.height, + 'Technology used with other departments:\n${data.departments?.map((item) => item.departmentName).toList() ?? '-'}'.bodyText(context), + ], 4.height, - 'Would other services be effected by acquiring the new equipment?:\n${data.effectedServices ?? '-'}'.bodyText(context), + 'Would other services be effected by acquiring the new equipment?:\n${data.isEffectedName ?? '-'}'.bodyText(context), + if ((data.effectedServices ?? "").isNotEmpty) ...[ + 4.height, + 'List of services would it be effected:\n${data.effectedServices ?? '-'}'.bodyText(context), + ], 4.height, - 'Is the equipment going to be used with combination of other equipment to accomplish a specific procedure?:\n${data.usedWithCombination ?? '-'}'.bodyText(context), + 'Is the equipment going to be used with combination of other equipment to accomplish a specific procedure?:\n${data.isCombinationName ?? '-'}'.bodyText(context), + if ((data.usedWithCombination ?? "").isNotEmpty) ...[ + 4.height, + 'Equipment going to be used with combination of other equipments:\n${data.usedWithCombination ?? '-'}'.bodyText(context), + ], ], ).toShadowContainer(context, borderRadius: 20, padding: 12); } @@ -153,16 +166,29 @@ class _TrafRequestDetailPageState extends State { '${context.translation.model}: ${data.modelName ?? '-'}'.bodyText(context), '${context.translation.manufacture}: ${data.manufacturerName ?? '-'}'.bodyText(context), if (data.requestTypeId == 733) ...[ - 'Last price & PO: ${data.poNumber ?? '-'}'.bodyText(context), - 'The quantity of the same asset: ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer - 'Existing asset under SLA: ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer - 'Age of the asset: ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer - 'Total Maintenance Cost (TMC): ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer - 'Net Book Value (NBV): ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer - 'Mean Time Between Failure (MTBF): ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer - 'Down Time (DT): ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer - 'Up Time (UT): ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer - 'Purchased Price: ${data.qty ?? '-'}'.bodyText(context), // todo check with shaheer + FutureBuilder( + future: Provider.of(context, listen: false).getAssetAnalytics(data.assetId!), + builder: (cxt, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const SizedBox.square(dimension: 24, child: CircularProgressIndicator()).center; + } + if (snapshot.hasData) { + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + 'Last price: ${snapshot.data?.lastPrice ?? '-'}'.bodyText(context), + 'PO: ${snapshot.data?.lastPONo ?? '-'}'.bodyText(context), + 'The quantity of the same asset: ${snapshot.data?.sameAssetQuantity ?? '-'}'.bodyText(context), // todo check with shaheer + 'Existing asset under SLA: ${snapshot.data?.slaStatus ?? '-'}'.bodyText(context), // todo check with shaheer + 'Age of the asset: ${snapshot.data?.assetAgeDescription ?? '-'}'.bodyText(context), // todo check with shaheer + 'Total Maintenance Cost (TMC): ${snapshot.data?.totalMaintenanceCost ?? '-'}'.bodyText(context), // todo check with shaheer + 'Net Book Value (NBV): ${snapshot.data?.netBookValue ?? '-'}'.bodyText(context), // todo check with shaheer + 'Mean Time Between Failure (MTBF): ${snapshot.data?.mtbfDescription ?? '-'}'.bodyText(context), // todo check with shaheer + 'Down Time (DT): ${snapshot.data?.downTimeDescription ?? '-'}'.bodyText(context), // todo check with shaheer + 'Up Time (UT): ${snapshot.data?.upTimeDescription ?? '-'}'.bodyText(context), + 'Purchased Price: ${snapshot.data?.purchasedPrice ?? '-'}'.bodyText(context), + ]); + } + return const SizedBox(); + }) ], ], ).toShadowContainer(context, borderRadius: 20, padding: 12); diff --git a/lib/modules/traf_module/traf_request_model.dart b/lib/modules/traf_module/traf_request_model.dart index 9234ba60..af3977be 100644 --- a/lib/modules/traf_module/traf_request_model.dart +++ b/lib/modules/traf_module/traf_request_model.dart @@ -51,6 +51,8 @@ class TrafRequestDataModel { String? employeeEmail; int? positionId; String? positionName; + String? departmentId; + String? departmentName; List? departments; List? trafDepartments; int? siteId; @@ -147,6 +149,8 @@ class TrafRequestDataModel { this.employeeEmail, this.positionId, this.positionName, + this.departmentId, + this.departmentName, this.departments, this.trafDepartments, this.siteId, @@ -243,6 +247,8 @@ class TrafRequestDataModel { employeeEmail = json['employeeEmail']; positionId = json['positionId']; positionName = json['positionName']; + departmentId = json['departmentId']; + departmentName = json['departmentName']; if (json['departments'] != null) { departments = []; json['departments'].forEach((v) { @@ -366,6 +372,8 @@ class TrafRequestDataModel { data['employeeEmail'] = this.employeeEmail; data['positionId'] = this.positionId; data['positionName'] = this.positionName; + data['departmentId'] = this.departmentId; + data['departmentName'] = this.departmentName; if (this.departments != null) { data['departments'] = this.departments!.map((v) => v.toJson()).toList(); } diff --git a/lib/modules/traf_module/traf_request_provider.dart b/lib/modules/traf_module/traf_request_provider.dart index 3626eab0..346f7351 100644 --- a/lib/modules/traf_module/traf_request_provider.dart +++ b/lib/modules/traf_module/traf_request_provider.dart @@ -5,6 +5,7 @@ import 'package:http/http.dart'; import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'asset_analytics_model.dart'; import 'traf_request_model.dart'; class TrafRequestProvider extends ChangeNotifier { @@ -43,4 +44,16 @@ class TrafRequestProvider extends ChangeNotifier { return -1; } } + + Future getAssetAnalytics(int assetId) async { + try { + Response response = await ApiManager.instance.get("${URLs.getAssetAnalytics}?assetId=$assetId"); + if (response.statusCode >= 200 && response.statusCode < 300) { + return AssetAnalytics.fromJson(json.decode(response.body)["data"]); + } + } catch (error) { + print(error); + } + return null; + } } diff --git a/lib/modules/traf_module/users_auto_complete_field.dart b/lib/modules/traf_module/users_auto_complete_field.dart index 12060844..c5d04db7 100644 --- a/lib/modules/traf_module/users_auto_complete_field.dart +++ b/lib/modules/traf_module/users_auto_complete_field.dart @@ -81,13 +81,13 @@ class _UsersAutoCompleteFieldState extends State { loading = true; }); } - List workOrders = (await _TechnologyUsersProvider.getUsersBasedOnSearch(textEditingValue.text)); + List workOrders = (await _TechnologyUsersProvider.getUsersBasedOnSearch(context.userProvider.user!.clientId!, textEditingValue.text)); setState(() { loading = false; }); return workOrders; }, - displayStringForOption: (UsersBasedOnSearchModel option) => widget.byName ? option.userName ?? "" : option.employeeId ?? "", + displayStringForOption: (UsersBasedOnSearchModel option) => widget.byName ? "${option.userName ?? "-"} (Nurse)" ?? "" : option.employeeId ?? "", fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { return TextField( controller: _controller, diff --git a/lib/new_views/common_widgets/multiple_item_drop_down_menu.dart b/lib/new_views/common_widgets/multiple_item_drop_down_menu.dart index d0df92fa..9adf0b97 100644 --- a/lib/new_views/common_widgets/multiple_item_drop_down_menu.dart +++ b/lib/new_views/common_widgets/multiple_item_drop_down_menu.dart @@ -210,7 +210,7 @@ class _MultipleItemDropDownMenuState requestsList(BuildContext context) { List list = []; - - if (context.userProvider.isAssessor) { + log('user type is ${context.userProvider.isQualityUser}'); + if (context.userProvider.isQualityUser) { + list.add(CreateRequestModel("Equipment Internal Audit Checklist".addTranslation, "add_icon", CreateEquipmentInternalAuditForm.id)); + list.add(CreateRequestModel("System Internal Audit Checklist".addTranslation, "add_icon", CreateSystemInternalAuditForm.id)); + } else if (context.userProvider.isAssessor) { list.add(CreateRequestModel("TRAF".addTranslation, "add_icon", CreateTRAFRequestPage.id)); } else if (context.userProvider.isEngineer) { if (Provider.of(context, listen: false).engineerCanCreateCM) { diff --git a/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart b/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart index 9cfce9d4..7305a81a 100644 --- a/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart +++ b/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart @@ -71,6 +71,8 @@ class _AllRequestsFilterPageState extends State { if (isEngineer) { types[context.translation.recurrentWo] = 5; + types["Equipment Internal Audit".addTranslation] = 10; + types["System Internal Audit".addTranslation] = 11; } if (context.settingProvider.isUserFlowMedical && isEngineer) { @@ -80,9 +82,13 @@ class _AllRequestsFilterPageState extends State { if (!isEngineer) { types['TRAF'] = 9; } + if (context.userProvider.isAssessor) { types = {"TRAF": 9}; } + if (context.userProvider.isQualityUser) { + types = {'Equipment Internal Audit': 10, 'System Internal Audit': 11}; + } final statuses = { "All WO": 0, diff --git a/lib/new_views/pages/land_page/my_request/my_requests_page.dart b/lib/new_views/pages/land_page/my_request/my_requests_page.dart index f24d1719..641c0243 100644 --- a/lib/new_views/pages/land_page/my_request/my_requests_page.dart +++ b/lib/new_views/pages/land_page/my_request/my_requests_page.dart @@ -1,5 +1,7 @@ //request main page +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/all_requests_provider.dart'; @@ -42,9 +44,11 @@ class _MyRequestsPageState extends State { Request(2, context.translation.gasRefill), Request(3, context.translation.transferAsset), Request(4, context.translation.preventiveMaintenance), + ]; if (Provider.of(context, listen: false).user!.type != UsersTypes.normal_user) { requestsList.add(Request(5, context.translation.recurrentWo)); + } //TODO unCommit this to enable task requestsList.add(Request(6, context.translation.taskRequest)); @@ -58,6 +62,11 @@ class _MyRequestsPageState extends State { if (context.userProvider.user!.type == UsersTypes.normal_user) { requestsList.add(Request(9, 'TRAF')); } + if (context.userProvider.isEngineer) { + // TODO need to replace with actual number. + requestsList.add(Request(10, 'Equipment Internal Audit')); + requestsList.add(Request(11, 'System Internal Audit')); + } if (context.userProvider.isAssessor) { requestsList = [ @@ -65,6 +74,14 @@ class _MyRequestsPageState extends State { Request(9, 'TRAF'), ]; } + if (context.userProvider.isQualityUser) { + requestsList = [ + Request(null, context.translation.allWorkOrder), + //TODO need to replace with actual number. + Request(10, 'Equipment Internal Audit'), + Request(11, 'System Internal Audit'), + ]; + } _provider = Provider.of(context, listen: false); _provider!.reset(); diff --git a/lib/new_views/pages/land_page/requests/request_paginated_listview.dart b/lib/new_views/pages/land_page/requests/request_paginated_listview.dart index e436e87d..750abfd7 100644 --- a/lib/new_views/pages/land_page/requests/request_paginated_listview.dart +++ b/lib/new_views/pages/land_page/requests/request_paginated_listview.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/models/new_models/dashboard_detail.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart'; import 'package:test_sa/modules/asset_inventory_module/pages/inventory_session_item_view.dart'; import 'package:test_sa/modules/tm_module/tasks_wo/task_request_item_view.dart'; import 'package:test_sa/modules/traf_module/traf_request_item_view.dart'; @@ -66,6 +68,10 @@ class RequestPaginatedListview extends StatelessWidget { return InventorySessionItemView(requestData: request); case 9: return TrafRequestItemView(requestData: request); + case 10: + return EquipmentInternalAuditItemView(requestData: request); + case 11: + return SystemInternalAuditItemView(requestData: request); default: return Container( height: 100, diff --git a/lib/new_views/pages/land_page/widgets/request_item_view_list.dart b/lib/new_views/pages/land_page/widgets/request_item_view_list.dart index b84406cc..b8d1f6eb 100644 --- a/lib/new_views/pages/land_page/widgets/request_item_view_list.dart +++ b/lib/new_views/pages/land_page/widgets/request_item_view_list.dart @@ -1,12 +1,17 @@ //request tab page +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/all_requests_and_count_model.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_item_view.dart'; +import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/system_internal_audit_item_view.dart'; import 'package:test_sa/modules/asset_inventory_module/pages/inventory_session_item_view.dart'; import 'package:test_sa/modules/tm_module/tasks_wo/task_request_item_view.dart'; import 'package:test_sa/modules/traf_module/traf_request_item_view.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/pages/land_page/requests/device_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/gas_refill_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/ppm_item_view.dart'; @@ -29,9 +34,19 @@ class RequestItemViewList extends StatelessWidget { shrinkWrap: true, itemBuilder: (cxt, index) { if (isLoading) return const SizedBox().toRequestShimmer(cxt, isLoading); + // if (list[index].transactionType == null) { + // return EquipmentInternalAuditItemView(requestDetails: list[index]); + // // return Container( + // // height: 100, + // // width: double.infinity, + // // color: AppColor.neutral40, + // // ); + // } switch (list[index].transactionType) { case 1: return ServiceRequestItemView(requestDetails: list[index]); + // case 1: + // return InternalAuditItemView(requestDetails: list[index]); case 2: return GasRefillItemView(requestDetails: list[index]); case 3: @@ -48,6 +63,11 @@ class RequestItemViewList extends StatelessWidget { return InventorySessionItemView(requestDetails: list[index]); case 9: return TrafRequestItemView(requestDetails: list[index]); + //Need to verify the type No.. + case 10: + return EquipmentInternalAuditItemView(requestDetails: list[index]); + case 11: + return SystemInternalAuditItemView(requestDetails: list[index]); default: Container( height: 100, diff --git a/lib/views/widgets/timer/app_timer.dart b/lib/views/widgets/timer/app_timer.dart index 84179d92..629ad60c 100644 --- a/lib/views/widgets/timer/app_timer.dart +++ b/lib/views/widgets/timer/app_timer.dart @@ -22,6 +22,7 @@ class AppTimer extends StatefulWidget { final TextStyle? style; final BoxDecoration? decoration; final bool enabled; + final bool showPicker; final String? label; final double? width; @@ -40,6 +41,7 @@ class AppTimer extends StatefulWidget { this.onPick, this.timerProgress, this.enabled = true, + this.showPicker = true, }) : super(key: key); @override @@ -126,7 +128,8 @@ class _AppTimerState extends State { @override void initState() { - canPickTime = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; + ///this is temp check for internal audit + canPickTime = widget.showPicker == false ? false : ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; _startAt = widget.timer?.startAt; _endAt = widget.timer?.endAt; _running = _startAt != null && _endAt == null; diff --git a/pubspec.lock b/pubspec.lock index 1693c075..44813421 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1634,6 +1634,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + tree_view_flutter: + dependency: "direct main" + description: + name: tree_view_flutter + sha256: "3365e29b7d1b1e6c5098a2a0e77a208a12a9345dfa73865cb4c87657cf387801" + url: "https://pub.dev" + source: hosted + version: "1.0.2" tuple: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 424aecc3..a7689979 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -95,8 +95,8 @@ dependencies: safe_device: ^1.2.1 toggle_switch: ^2.3.0 clipboard: ^2.0.2 + tree_view_flutter: ^1.0.2 audio_waveforms: ^1.3.0 - signalr_netcore: ^1.4.4 local_auth_darwin: any