From 58ee00b00af9e0b4b1cac2d9f05f37e10a7a4758 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 8 Feb 2026 15:03:43 +0300 Subject: [PATCH 01/11] incident module dev -1 --- lib/controllers/api_routes/urls.dart | 6 + lib/main.dart | 10 +- .../create_incident_request_page.dart | 572 ++++++++++++++++++ .../incident_lookup_provider.dart | 158 +++++ .../incident_type_lookup_provider.dart | 33 + .../create_request_type_bottom_sheet.dart | 4 + 6 files changed, 782 insertions(+), 1 deletion(-) create mode 100644 lib/modules/incident_module/create_incident_request_page.dart create mode 100644 lib/modules/incident_module/incident_lookup_provider.dart create mode 100644 lib/modules/incident_module/incident_type_lookup_provider.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index c07b1f03..17e0f4b6 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -251,6 +251,12 @@ class URLs { static get getYesNoRequestTypeLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=4"; static get getDemoPeriodLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=4101"; + static get incidentStatusLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3060"; + static get incidentTypeLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3061"; + static get incidentRootCauseLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3062"; + static get incidentPersonInvolvedLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3063"; + static get clinicalNonClinicalLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3064"; + static get genderLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3065"; static get getClassificationTypeLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=450"; diff --git a/lib/main.dart b/lib/main.dart index 20701c5c..f6e9a9fb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -119,6 +119,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/incident_module/create_incident_request_page.dart'; +import 'modules/incident_module/incident_lookup_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'; @@ -343,6 +345,12 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => MedicalGasSupplierProvider()), ChangeNotifierProvider(create: (_) => DemoProvider()), ChangeNotifierProvider(create: (_) => DemoPeriodLookupProvider()), + ChangeNotifierProvider(create: (_) => IncidentStatusLookupProvider()), + ChangeNotifierProvider(create: (_) => IncidentTypeLookupProvider()), + ChangeNotifierProvider(create: (_) => IncidentRootCauseLookupProvider()), + ChangeNotifierProvider(create: (_) => IncidentPersonInvolvedLookupProvider()), + ChangeNotifierProvider(create: (_) => ClinicalNonClinicalLookupProvider()), + ChangeNotifierProvider(create: (_) => GenderLookupProvider()), ], child: GestureDetector( onTap: () { @@ -398,7 +406,7 @@ class MyApp extends StatelessWidget { AssetDetailPage.id: (_) => const AssetDetailPage(), DeviceSearchScreen.id: (_) => const DeviceSearchScreen(), DeviceFilterScreen.id: (_) => const DeviceFilterScreen(), - // WorkOrderDetailsPage.id: (_) => const WorkOrderDetailsPage(), + CreateIncidentRequestPage.id: (_) => CreateIncidentRequestPage(), // UpdateServiceRequestPage.id: (_) => const UpdateServiceRequestPage(), SettingsPage.id: (_) => const SettingsPage(), diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart new file mode 100644 index 00000000..817ffb44 --- /dev/null +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -0,0 +1,572 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/controllers/validator/validator.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/lookup.dart'; +import 'package:test_sa/models/new_models/building.dart'; +import 'package:test_sa/models/new_models/floor.dart'; +import 'package:test_sa/models/new_models/site.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/incident_module/incident_lookup_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_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; +import 'package:test_sa/providers/gas_request_providers/site_provider.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; +import 'package:test_sa/providers/lookups/yes_no_lookup_provider.dart'; +import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; + +import '../../models/new_models/department.dart'; + +class CreateIncidentRequestPage extends StatefulWidget { + static const String id = "/create-incident"; + + CreateIncidentRequestPage({Key? key}) : super(key: key); + + @override + _CreateIncidentRequestPageState createState() { + return _CreateIncidentRequestPageState(); + } +} + +class _CreateIncidentRequestPageState extends State { + final GlobalKey _formKey = GlobalKey(); + final GlobalKey _scaffoldKey = GlobalKey(); + final List attachments = []; + + Site? _selectedSite; + Building? _selectedBuilding; + Floor? _selectedFloor; + Department? _selectedDepartment; + + Asset? device; + + Lookup? incidentType; + Lookup? ovrSystem; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: DefaultAppBar(title: 'Incident'.addTranslation), + body: Form( + key: _formKey, + child: Column( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + ...incidentInfoSection(), + if (incidentType?.value == 1) ...[ + 16.height, + AssetPicker( + device: device, + showLoading: false, + borderColor: AppColor.black20, + onPick: (asset) async { + device = asset; + setState(() {}); + }, + ) + ], + if (incidentType?.value != 1) ...[ + 16.height, + ...siteSection(), + ], + 16.height, + ...vendorDetailsSection(), + 16.height, + AttachmentPicker( + label: context.translation.attachments, + attachment: attachments, + buttonColor: AppColor.black10, + onlyImages: false, + showAsListView: true, + buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), + onChange: (attachments) { + attachments = attachments; + setState(() {}); + }, + ), + ], + ).toShadowContainer(context, borderRadius: 20), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: context.translation.submitRequest, + onPressed: _submit, + // buttonColor: AppColor.primary10, + ), + ), + ], + ), + ), + ); + } + + List siteSection() { + return [ + 'Site Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), + 8.height, + SingleItemDropDownMenu( + context: context, + title: context.translation.site, + initialValue: _selectedSite, + showShadow: false, + validator: (value) { + if (value == null) return "Please select a site"; + return null; + }, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + onSelect: (value) { + _selectedSite = value; + setState(() {}); + }, + ), + 8.height, + SingleItemDropDownMenu( + context: context, + title: context.translation.building, + initialValue: _selectedBuilding, + showShadow: false, + showAsBottomSheet: true, + validator: (value) { + if (value == null) return "Please select a building"; + return null; + }, + backgroundColor: AppColor.fieldBgColor(context), + enabled: _selectedSite?.buildings?.isNotEmpty ?? false, + staticData: _selectedSite?.buildings ?? [], + onSelect: (value) { + _selectedBuilding = value; + _selectedFloor = null; + _selectedDepartment = null; + setState(() {}); + }, + ), + 8.height, + SingleItemDropDownMenu( + context: context, + title: context.translation.floor, + showShadow: false, + showAsBottomSheet: true, + initialValue: _selectedFloor, + backgroundColor: AppColor.fieldBgColor(context), + enabled: _selectedBuilding?.floors?.isNotEmpty ?? false, + staticData: _selectedBuilding?.floors ?? [], + validator: (value) { + if (value == null) return "Please select a floor"; + return null; + }, + onSelect: (value) { + _selectedFloor = value; + _selectedDepartment = null; + setState(() {}); + }, + ), + 8.height, + SingleItemDropDownMenu( + context: context, + title: context.translation.department, + showShadow: false, + showAsBottomSheet: true, + initialValue: _selectedDepartment, + backgroundColor: AppColor.fieldBgColor(context), + enabled: _selectedFloor?.departments?.isNotEmpty ?? false, + staticData: _selectedFloor?.departments ?? [], + validator: (value) { + if (value == null) return "Please select a department"; + return null; + }, + onSelect: (value) { + _selectedDepartment = value; + // _demoFormModel.room = null; + setState(() {}); + }, + ), + ]; + } + + // List siteSection() { + // return [ + // 'Site Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), + // 8.height, + // SingleItemDropDownMenu( + // context: context, + // title: context.translation.site, + // initialValue: _demoFormModel.site, + // showShadow: false, + // validator: (value) { + // if (value == null) return "Please select a site"; + // return null; + // }, + // backgroundColor: AppColor.fieldBgColor(context), + // showAsBottomSheet: true, + // onSelect: (value) { + // _demoFormModel.site = value; + // setState(() {}); + // }, + // ), + // 8.height, + // SingleItemDropDownMenu( + // context: context, + // title: context.translation.department, + // showShadow: false, + // validator: (value) { + // if (value == null) return "Please select a department"; + // return null; + // }, + // showAsBottomSheet: true, + // initialValue: _demoFormModel.department, + // requestById: context.userProvider.user?.clientId, + // backgroundColor: AppColor.fieldBgColor(context), + // onSelect: (value) { + // _demoFormModel.department = value; + // setState(() {}); + // }, + // ), + // ]; + // } + + List incidentInfoSection() { + return [ + "Incident Details".bodyText(context).custom(color: AppColor.black10), + 8.height, + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: "Incident Type", + initialValue: incidentType, + showShadow: false, + validator: (value) { + if (value == null) return "Mandatory"; + return null; + }, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + onSelect: (value) { + incidentType = value; + setState(() {}); + }, + ), + 8.height, + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: "Is there any existing OVR System?", + initialValue: ovrSystem, + showShadow: false, + validator: (value) { + if (value == null) return "Mandatory"; + return null; + }, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + onSelect: (value) { + ovrSystem = value; + setState(() {}); + }, + ), + if (ovrSystem?.value == 1) ...[ + 8.height, + AppTextFormField( + initialValue: "", + labelText: "OVR Ticket Number", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.name, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.vendorRepresentativeName = value; + }, + ), + ], + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Name", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.text, + onChange: (value) { + // _demoFormModel.docName = value; + }, + ), + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Contact Number", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.phone, + onChange: (value) { + // _demoFormModel.docNumber = value; + }, + ), + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Email", + validator: (value) { + if ((value ?? "").isEmpty) { + return "Mandatory"; + } else { + return Validator.isEmail(value!) ? null : context.translation.emailValidateMessage; + } + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.emailAddress, + onChange: (value) { + // _demoFormModel.docEmail = value; + }, + ), + 8.height, + AppTextFormField( + labelText: "Item Description", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.text, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.itemDescription = value; + }, + ), + 8.height, + AppTextFormField( + labelText: "Request Description", + // validator: (value) { + // if ((value ?? "").isEmpty) return "Mandatory"; + // return null; + // }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.text, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.requestDescription = value; + }, + ), + ]; + } + + List assetSection() { + return [ + 'Asset Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), + 8.height, + AppTextFormField( + labelText: "Model", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.text, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.model = value; + }, + ), + 8.height, + AppTextFormField( + labelText: "Manufacturer", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.text, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.manufacturer = value; + }, + ), + // 8.height, + // SingleItemDropDownMenu( + // context: context, + // height: 56.toScreenHeight, + // title: "Demo Period", + // initialValue: demoPeriodLookup, + // showShadow: false, + // validator: (value) { + // if (value == null) return "Mandatory"; + // return null; + // }, + // backgroundColor: AppColor.fieldBgColor(context), + // showAsBottomSheet: true, + // onSelect: (value) { + // demoPeriodLookup = value; + // setState(() {}); + // }, + // ), + ]; + } + + List vendorDetailsSection() { + return [ + 'Vendor Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Incident Title", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.name, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.vendorName = value; + }, + ), + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Incident Description", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.name, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.vendorRepresentativeName = value; + }, + ), + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Contact Number", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.phone, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.vendorNumber = value; + }, + ), + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Email", + validator: (value) { + if ((value ?? "").isEmpty) { + return "Mandatory"; + } else { + return Validator.isEmail(value!) ? null : context.translation.emailValidateMessage; + } + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + onChange: (value) { + // _demoFormModel.vendorEmail = value; + }, + ), + ]; + } + + Future _submit() async { + FocusScope.of(context).unfocus(); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + + // _demoFormModel.demoAttachment = []; + // for (var item in attachments) { + // String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + // _demoFormModel.demoAttachment?.add(DemoAttachments(id: 0, attachmentName: fileName, demoRequestId: 0)); + // } + // Utils.showLoading(context); + // LoanProvider loanProvider = Provider.of(context, listen: false); + // Map body = _demoFormModel.toJson(); + // body["id"] = 0; + // // body["LoanTypeId"] = 0; + // // body["requestorUserID"] = context.userProvider.user!.userID; + // + // bool isSuccess = await loanProvider.addLoanRequest(body); + // Utils.hideLoading(context); + // if (isSuccess) { + // Navigator.pop(context); + // } + } + } +} diff --git a/lib/modules/incident_module/incident_lookup_provider.dart b/lib/modules/incident_module/incident_lookup_provider.dart new file mode 100644 index 00000000..9798b328 --- /dev/null +++ b/lib/modules/incident_module/incident_lookup_provider.dart @@ -0,0 +1,158 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +import '../../controllers/api_routes/api_manager.dart'; +import '../../controllers/api_routes/urls.dart'; +import '../../models/lookup.dart'; + +class IncidentStatusLookupProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.incidentStatusLookup); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => Lookup.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} + +class IncidentTypeLookupProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.incidentTypeLookup); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => Lookup.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} + +class IncidentRootCauseLookupProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.incidentRootCauseLookup); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => Lookup.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} + +class IncidentPersonInvolvedLookupProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.incidentPersonInvolvedLookup); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => Lookup.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} + +class ClinicalNonClinicalLookupProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.clinicalNonClinicalLookup); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => Lookup.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} + +class GenderLookupProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.genderLookup); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => Lookup.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} diff --git a/lib/modules/incident_module/incident_type_lookup_provider.dart b/lib/modules/incident_module/incident_type_lookup_provider.dart new file mode 100644 index 00000000..9ee4b44b --- /dev/null +++ b/lib/modules/incident_module/incident_type_lookup_provider.dart @@ -0,0 +1,33 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +import '../../controllers/api_routes/api_manager.dart'; +import '../../controllers/api_routes/urls.dart'; +import '../../models/lookup.dart'; + +class DemoPeriodLookupProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.incidentTypeLookup); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => Lookup.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} diff --git a/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart b/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart index 26cb28a3..f1d8fefc 100644 --- a/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart +++ b/lib/new_views/pages/land_page/create_request_type_bottom_sheet.dart @@ -11,6 +11,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/models/enums/user_types.dart'; import 'package:test_sa/modules/cm_module/create_cm_request.dart'; import 'package:test_sa/modules/demo_module/create_demo_request_page.dart'; +import 'package:test_sa/modules/incident_module/create_incident_request_page.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/loan_module/pages/create_loan_request_page.dart'; @@ -142,6 +143,9 @@ class CreateRequestModel { list.add(CreateRequestModel('Loan Equipment Request'.addTranslation, "add_icon", CreateLoanRequestPage.id)); list.add(CreateRequestModel('Demo Request'.addTranslation, "add_icon", CreateDemoRequestPage.id)); } + if (!context.userProvider.isNurse && context.settingProvider.isUserFlowMedical) { + list.add(CreateRequestModel("Incident Management", "add_icon", CreateIncidentRequestPage.id)); + } return list; } } From cb2329a6c21d2ab70c64527a8269d882e7f705b4 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 9 Feb 2026 16:45:01 +0300 Subject: [PATCH 02/11] incident create request completed. --- lib/controllers/api_routes/urls.dart | 2 + lib/main.dart | 2 + lib/models/device/model_definition.dart | 39 ++ lib/modules/demo_module/demo_provider.dart | 2 +- .../create_incident_request_page.dart | 446 +++++++++--------- .../incident_attachment_model.dart | 24 + .../incident_module/incident_provider.dart | 38 ++ .../loan_module/provider/loan_provider.dart | 2 +- 8 files changed, 343 insertions(+), 212 deletions(-) create mode 100644 lib/modules/incident_module/incident_attachment_model.dart create mode 100644 lib/modules/incident_module/incident_provider.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 17e0f4b6..10ea73dd 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -408,8 +408,10 @@ class URLs { static get getMedDepartmentBasedOnSite => "$_baseUrl/TRAFDataSource/GetDepartmentBasedOnSite"; static get getLoanById => '$_baseUrl/Loan/GetLoanById'; + static get getIncidentById => '$_baseUrl/Incident/GetIncidentById'; static get addLoan => '$_baseUrl/Loan/AddLoan'; + static get addIncident => '$_baseUrl/Incident/AddIncident'; //Asset Delivery Module static get getAssetDeliveryDetailsById => '$_baseUrl/AssetDeliveryExternal/GetAssetDeliveryExternalById'; diff --git a/lib/main.dart b/lib/main.dart index f6e9a9fb..1d64b423 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,6 +40,7 @@ import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; import 'package:test_sa/modules/demo_module/create_demo_request_page.dart'; import 'package:test_sa/modules/demo_module/demo_period_lookup_provider.dart'; import 'package:test_sa/modules/demo_module/demo_provider.dart'; +import 'package:test_sa/modules/incident_module/incident_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'; @@ -351,6 +352,7 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => IncidentPersonInvolvedLookupProvider()), ChangeNotifierProvider(create: (_) => ClinicalNonClinicalLookupProvider()), ChangeNotifierProvider(create: (_) => GenderLookupProvider()), + ChangeNotifierProvider(create: (_) => IncidentProvider()), ], child: GestureDetector( onTap: () { diff --git a/lib/models/device/model_definition.dart b/lib/models/device/model_definition.dart index a19a7a18..328909b8 100644 --- a/lib/models/device/model_definition.dart +++ b/lib/models/device/model_definition.dart @@ -37,6 +37,13 @@ class ModelDefinition { modelDefRelatedDefects!.add(ModelDefRelatedDefects.fromJson(v)); // Use '!' since modelDefRelatedDefects is initialized here }); } + + if (json['oracleCodes'] != null) { + oracleCodes = []; + json['oracleCodes'].forEach((v) { + oracleCodes!.add(OracleCode.fromJson(v)); // Use '!' since modelDefRelatedDefects is initialized here + }); + } if (json['suppliers'] != null) { suppliers = []; json['suppliers'].forEach((v) { @@ -59,6 +66,7 @@ class ModelDefinition { num? lifeSpan; // Now nullable List? modelDefRelatedDefects; // Now nullable List? suppliers; // Now nullable + List? oracleCodes; // Now nullable // ... (copyWith method remains the same, just with nullable parameters) @@ -108,3 +116,34 @@ class ModelDefRelatedDefects { return map; } } + +class OracleCode { + int? id; + int? assetNDId; + int? codeTypeId; + String? codeTypeName; + int? codeTypeValue; + String? codeValue; + + OracleCode({this.id, this.assetNDId, this.codeTypeId, this.codeTypeName, this.codeTypeValue, this.codeValue}); + + OracleCode.fromJson(Map json) { + id = json['id']; + assetNDId = json['assetNDId']; + codeTypeId = json['codeTypeId']; + codeTypeName = json['codeTypeName']; + codeTypeValue = json['codeTypeValue']; + codeValue = json['codeValue']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['assetNDId'] = this.assetNDId; + data['codeTypeId'] = this.codeTypeId; + data['codeTypeName'] = this.codeTypeName; + data['codeTypeValue'] = this.codeTypeValue; + data['codeValue'] = this.codeValue; + return data; + } +} diff --git a/lib/modules/demo_module/demo_provider.dart b/lib/modules/demo_module/demo_provider.dart index 18e64600..453df42b 100644 --- a/lib/modules/demo_module/demo_provider.dart +++ b/lib/modules/demo_module/demo_provider.dart @@ -11,7 +11,7 @@ class DemoProvider extends ChangeNotifier { try { Response response = await ApiManager.instance.post(URLs.addLoan, body: body, showToast: false); if (response.statusCode >= 200 && response.statusCode < 300) { - String message = (jsonDecode(response.body)["data"] ?? "") + " " + (jsonDecode(response.body)["message"] ?? ""); + String message = (jsonDecode(response.body)["message"] ?? ""); Fluttertoast.showToast(msg: message ?? "", toastLength: Toast.LENGTH_LONG); return true; } diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 817ffb44..45a5eb86 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -1,18 +1,27 @@ +import 'dart:convert'; +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:test_sa/controllers/validator/validator.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/helper/utils.dart'; import 'package:test_sa/models/device/asset.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/new_models/building.dart'; import 'package:test_sa/models/new_models/floor.dart'; import 'package:test_sa/models/new_models/site.dart'; +import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/incident_module/incident_lookup_provider.dart'; +import 'package:test_sa/modules/incident_module/incident_provider.dart'; +import 'package:test_sa/modules/loan_module/models/medical_department_model.dart'; +import 'package:test_sa/modules/loan_module/provider/medical_department_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_text_form_field.dart'; @@ -21,10 +30,12 @@ import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart import 'package:test_sa/providers/gas_request_providers/site_provider.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/providers/lookups/yes_no_lookup_provider.dart'; +import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import '../../models/new_models/department.dart'; +import 'incident_attachment_model.dart'; class CreateIncidentRequestPage extends StatefulWidget { static const String id = "/create-incident"; @@ -40,8 +51,10 @@ class CreateIncidentRequestPage extends StatefulWidget { class _CreateIncidentRequestPageState extends State { final GlobalKey _formKey = GlobalKey(); final GlobalKey _scaffoldKey = GlobalKey(); - final List attachments = []; + List attachments = []; + MedicalDepartmentModel? _medicalDepartmentModel; + Site? _empSite; Site? _selectedSite; Building? _selectedBuilding; Floor? _selectedFloor; @@ -51,6 +64,12 @@ class _CreateIncidentRequestPageState extends State { Lookup? incidentType; Lookup? ovrSystem; + Lookup? rootCause; + Lookup? involvedPerson; + Lookup? gender; + DateTime? occurrenceDate; + + Map payload = {}; @override void initState() { @@ -77,26 +96,39 @@ class _CreateIncidentRequestPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + ...empSiteSection(), + 16.height, ...incidentInfoSection(), if (incidentType?.value == 1) ...[ 16.height, + "Asset Details".bodyText(context).custom(color: AppColor.black10), + 8.height, AssetPicker( device: device, showLoading: false, borderColor: AppColor.black20, onPick: (asset) async { device = asset; + payload["assetId"] = asset.modelDefinition?.id; + payload["oracleCode"] = asset.modelDefinition?.oracleCodes?.first.codeValue; + payload["model"] = asset.modelDefinition?.modelName; + payload["manufacturer"] = asset.modelDefinition?.manufacturerId; + payload["assetOrigin"] = asset.modelDefinition?.assetName; setState(() {}); }, ) ], - if (incidentType?.value != 1) ...[ + if (incidentType?.value != null && incidentType?.value != 1) ...[ 16.height, ...siteSection(), ], 16.height, - ...vendorDetailsSection(), + ...detailsSection(), 16.height, + ...optionQuestions(), + 16.height, + "Attachments".bodyText(context).custom(color: AppColor.black10), + 8.height, AttachmentPicker( label: context.translation.attachments, attachment: attachments, @@ -104,8 +136,8 @@ class _CreateIncidentRequestPageState extends State { onlyImages: false, showAsListView: true, buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), - onChange: (attachments) { - attachments = attachments; + onChange: (_attachments) { + attachments = _attachments; setState(() {}); }, ), @@ -127,6 +159,27 @@ class _CreateIncidentRequestPageState extends State { ); } + List optionQuestions() { + return [ + 'Additional Information & Comments'.addTranslation.bodyText(context).custom(color: AppColor.black10), + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Some Comments", + style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff3B3D4A)), + labelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff767676)), + floatingLabelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: context.isDark ? Colors.white : const Color(0xff3B3D4A)), + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + textInputType: TextInputType.multiline, + makeMultiLinesNull: false, + onChange: (value) { + payload["comments"] = value; + }, + ), + ]; + } + List siteSection() { return [ 'Site Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), @@ -144,6 +197,7 @@ class _CreateIncidentRequestPageState extends State { showAsBottomSheet: true, onSelect: (value) { _selectedSite = value; + payload["siteId"] = value?.id; setState(() {}); }, ), @@ -165,6 +219,7 @@ class _CreateIncidentRequestPageState extends State { _selectedBuilding = value; _selectedFloor = null; _selectedDepartment = null; + payload["buildingId"] = value?.id; setState(() {}); }, ), @@ -185,6 +240,7 @@ class _CreateIncidentRequestPageState extends State { onSelect: (value) { _selectedFloor = value; _selectedDepartment = null; + payload["floorId"] = value?.id; setState(() {}); }, ), @@ -204,53 +260,53 @@ class _CreateIncidentRequestPageState extends State { }, onSelect: (value) { _selectedDepartment = value; - // _demoFormModel.room = null; + payload["departmentId"] = value?.id; setState(() {}); }, ), ]; } - // List siteSection() { - // return [ - // 'Site Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), - // 8.height, - // SingleItemDropDownMenu( - // context: context, - // title: context.translation.site, - // initialValue: _demoFormModel.site, - // showShadow: false, - // validator: (value) { - // if (value == null) return "Please select a site"; - // return null; - // }, - // backgroundColor: AppColor.fieldBgColor(context), - // showAsBottomSheet: true, - // onSelect: (value) { - // _demoFormModel.site = value; - // setState(() {}); - // }, - // ), - // 8.height, - // SingleItemDropDownMenu( - // context: context, - // title: context.translation.department, - // showShadow: false, - // validator: (value) { - // if (value == null) return "Please select a department"; - // return null; - // }, - // showAsBottomSheet: true, - // initialValue: _demoFormModel.department, - // requestById: context.userProvider.user?.clientId, - // backgroundColor: AppColor.fieldBgColor(context), - // onSelect: (value) { - // _demoFormModel.department = value; - // setState(() {}); - // }, - // ), - // ]; - // } + List empSiteSection() { + return [ + SingleItemDropDownMenu( + context: context, + title: "Employee Site", + initialValue: _empSite, + showShadow: false, + validator: (value) { + if (value == null) return "Please select a site"; + return null; + }, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + onSelect: (value) { + _empSite = value; + payload["requestorSiteId"] = value?.id; + setState(() {}); + }, + ), + 8.height, + SingleItemDropDownMenu( + context: context, + title: "Employee Department", + showShadow: false, + validator: (value) { + if (value == null) return "Please select a department"; + return null; + }, + showAsBottomSheet: true, + initialValue: _medicalDepartmentModel, + requestById: context.userProvider.user?.clientId, + backgroundColor: AppColor.fieldBgColor(context), + onSelect: (value) { + _medicalDepartmentModel = value; + payload["requestorFlowDepartmentId"] = value?.id; + setState(() {}); + }, + ), + ]; + } List incidentInfoSection() { return [ @@ -270,6 +326,7 @@ class _CreateIncidentRequestPageState extends State { showAsBottomSheet: true, onSelect: (value) { incidentType = value; + payload["incidentClassificationId"] = value?.id; setState(() {}); }, ), @@ -288,6 +345,7 @@ class _CreateIncidentRequestPageState extends State { showAsBottomSheet: true, onSelect: (value) { ovrSystem = value; + payload["existingOvrTicketId"] = value?.id; setState(() {}); }, ), @@ -307,31 +365,14 @@ class _CreateIncidentRequestPageState extends State { textInputType: TextInputType.name, textInputAction: TextInputAction.next, onChange: (value) { - // _demoFormModel.vendorRepresentativeName = value; + payload["ovrTicketNumber"] = value; }, ), ], 8.height, AppTextFormField( initialValue: "", - labelText: "Name", - validator: (value) { - if ((value ?? "").isEmpty) return "Mandatory"; - return null; - }, - style: Theme.of(context).textTheme.titleMedium, - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.text, - onChange: (value) { - // _demoFormModel.docName = value; - }, - ), - 8.height, - AppTextFormField( - initialValue: "", - labelText: "Contact Number", + labelText: "Incident Title", validator: (value) { if ((value ?? "").isEmpty) return "Mandatory"; return null; @@ -340,34 +381,16 @@ class _CreateIncidentRequestPageState extends State { backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, - textInputType: TextInputType.phone, + textInputType: TextInputType.name, + textInputAction: TextInputAction.next, onChange: (value) { - // _demoFormModel.docNumber = value; + payload["incidentTitle"] = value; }, ), 8.height, AppTextFormField( initialValue: "", - labelText: "Email", - validator: (value) { - if ((value ?? "").isEmpty) { - return "Mandatory"; - } else { - return Validator.isEmail(value!) ? null : context.translation.emailValidateMessage; - } - }, - style: Theme.of(context).textTheme.titleMedium, - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.emailAddress, - onChange: (value) { - // _demoFormModel.docEmail = value; - }, - ), - 8.height, - AppTextFormField( - labelText: "Item Description", + labelText: "Incident Description", validator: (value) { if ((value ?? "").isEmpty) return "Mandatory"; return null; @@ -375,171 +398,167 @@ class _CreateIncidentRequestPageState extends State { style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.text, - textInputAction: TextInputAction.next, - onChange: (value) { - // _demoFormModel.itemDescription = value; - }, - ), - 8.height, - AppTextFormField( - labelText: "Request Description", - // validator: (value) { - // if ((value ?? "").isEmpty) return "Mandatory"; - // return null; - // }, - style: Theme.of(context).textTheme.titleMedium, - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, - textInputType: TextInputType.text, + textInputType: TextInputType.name, textInputAction: TextInputAction.next, onChange: (value) { - // _demoFormModel.requestDescription = value; + payload["incidentDescription"] = value; }, ), ]; } - List assetSection() { + List detailsSection() { return [ - 'Asset Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), + 'Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), 8.height, - AppTextFormField( - labelText: "Model", + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: "Root Cause", + initialValue: rootCause, + showShadow: false, validator: (value) { - if ((value ?? "").isEmpty) return "Mandatory"; + if (value == null) return "Mandatory"; return null; }, - style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.text, - textInputAction: TextInputAction.next, - onChange: (value) { - // _demoFormModel.model = value; + showAsBottomSheet: true, + onSelect: (value) { + rootCause = value; + payload["rootCauseId"] = value?.id; + setState(() {}); }, ), + if (rootCause?.value == 5) ...[ + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Root Cause (Other)", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.name, + textInputAction: TextInputAction.next, + onChange: (value) { + payload["otherRootCause"] = value; + }, + ), + ], 8.height, - AppTextFormField( - labelText: "Manufacturer", + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: "Person Involved", + initialValue: involvedPerson, + showShadow: false, validator: (value) { - if ((value ?? "").isEmpty) return "Mandatory"; + if (value == null) return "Mandatory"; return null; }, - style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - floatingLabelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.text, - textInputAction: TextInputAction.next, - onChange: (value) { - // _demoFormModel.manufacturer = value; + showAsBottomSheet: true, + onSelect: (value) { + involvedPerson = value; + payload["personInvolvedId"] = value?.id; }, ), - // 8.height, - // SingleItemDropDownMenu( - // context: context, - // height: 56.toScreenHeight, - // title: "Demo Period", - // initialValue: demoPeriodLookup, - // showShadow: false, - // validator: (value) { - // if (value == null) return "Mandatory"; - // return null; - // }, - // backgroundColor: AppColor.fieldBgColor(context), - // showAsBottomSheet: true, - // onSelect: (value) { - // demoPeriodLookup = value; - // setState(() {}); - // }, - // ), - ]; - } - - List vendorDetailsSection() { - return [ - 'Vendor Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), 8.height, - AppTextFormField( - initialValue: "", - labelText: "Incident Title", + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: "Gender", + initialValue: gender, + showShadow: false, validator: (value) { - if ((value ?? "").isEmpty) return "Mandatory"; + if (value == null) return "Mandatory"; return null; }, - style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.name, - textInputAction: TextInputAction.next, - onChange: (value) { - // _demoFormModel.vendorName = value; + showAsBottomSheet: true, + onSelect: (value) { + gender = value; + payload["genderId"] = value?.id; }, ), 8.height, AppTextFormField( initialValue: "", - labelText: "Incident Description", + labelText: "Person Involved ID", validator: (value) { - if ((value ?? "").isEmpty) return "Mandatory"; + if (involvedPerson?.value == 3 && (value ?? "").isEmpty) return "Mandatory"; return null; }, style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, - textInputType: TextInputType.name, + textInputType: TextInputType.number, textInputAction: TextInputAction.next, onChange: (value) { - // _demoFormModel.vendorRepresentativeName = value; + payload["personInvolvedEmployeeId"] = value; }, ), 8.height, AppTextFormField( initialValue: "", - labelText: "Contact Number", + labelText: "Person Involved Name", validator: (value) { - if ((value ?? "").isEmpty) return "Mandatory"; + if (involvedPerson?.value == 3 && (value ?? "").isEmpty) return "Mandatory"; return null; }, style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, - textInputType: TextInputType.phone, + textInputType: TextInputType.name, textInputAction: TextInputAction.next, onChange: (value) { - // _demoFormModel.vendorNumber = value; + payload["personInvolvedEmployeeName"] = value; }, ), 8.height, - AppTextFormField( - initialValue: "", - labelText: "Email", - validator: (value) { - if ((value ?? "").isEmpty) { - return "Mandatory"; - } else { - return Validator.isEmail(value!) ? null : context.translation.emailValidateMessage; - } - }, - style: Theme.of(context).textTheme.titleMedium, - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.emailAddress, - textInputAction: TextInputAction.next, - onChange: (value) { - // _demoFormModel.vendorEmail = value; + ADatePicker( + label: "Occurrence Date & Time", + hideShadow: true, + backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, + date: occurrenceDate, + from: DateTime.now().subtract(const Duration(days: 365)), + // enable: widget.enabled ? _tempPickerTimer == null : false, + formatDateWithTime: true, + onDatePicker: (selectedDate) { + showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + builder: (BuildContext context, Widget? child) { + final ThemeData currentTheme = Theme.of(context); + return Theme( + data: currentTheme.copyWith( + timePickerTheme: TimePickerThemeData( + dialHandColor: AppColor.primary10, + dialBackgroundColor: Colors.grey.withOpacity(0.1), + hourMinuteColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? AppColor.primary10 : Colors.grey.withOpacity(0.1)), + dayPeriodColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? AppColor.primary10 : Colors.transparent), + dayPeriodTextColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? Colors.white : AppColor.primary10), + dayPeriodBorderSide: BorderSide(color: Colors.grey.withOpacity(0.2)), + entryModeIconColor: AppColor.primary10, + ), + textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(foregroundColor: AppColor.primary10)), + ), + child: child!, + ); + }, + ).then((selectedTime) { + if (selectedTime != null) { + occurrenceDate = DateTime(selectedDate.year, selectedDate.month, selectedDate.day, selectedTime.hour, selectedTime.minute); + setState(() {}); + } + }); }, ), ]; @@ -548,25 +567,32 @@ class _CreateIncidentRequestPageState extends State { Future _submit() async { FocusScope.of(context).unfocus(); if (_formKey.currentState!.validate()) { + if (occurrenceDate == null) { + "Please select occurrence Data".showToast; + return; + } + + payload["occurrenceDate"] = occurrenceDate!.toIso8601String(); _formKey.currentState!.save(); - // _demoFormModel.demoAttachment = []; - // for (var item in attachments) { - // String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; - // _demoFormModel.demoAttachment?.add(DemoAttachments(id: 0, attachmentName: fileName, demoRequestId: 0)); - // } - // Utils.showLoading(context); - // LoanProvider loanProvider = Provider.of(context, listen: false); - // Map body = _demoFormModel.toJson(); - // body["id"] = 0; - // // body["LoanTypeId"] = 0; - // // body["requestorUserID"] = context.userProvider.user!.userID; - // - // bool isSuccess = await loanProvider.addLoanRequest(body); - // Utils.hideLoading(context); - // if (isSuccess) { - // Navigator.pop(context); - // } + List attachmentList = []; + + for (var item in attachments) { + String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; + attachmentList.add(IncidentAttachments(id: 0, attachmentName: fileName, incidentId: 0)); + } + Utils.showLoading(context); + IncidentProvider incidentProvider = Provider.of(context, listen: false); + payload["id"] = 0; + payload["isFromMobile"] = 0; + payload["requestorUserId"] = context.userProvider.user!.userID; + payload["requestorSiteId"] = context.userProvider.user!.clientId; + payload["incidentAttachments"] = attachmentList.map((v) => v.toJson()).toList(); + bool isSuccess = await incidentProvider.addIncidentRequest(payload); + Utils.hideLoading(context); + if (isSuccess) { + Navigator.pop(context); + } } } } diff --git a/lib/modules/incident_module/incident_attachment_model.dart b/lib/modules/incident_module/incident_attachment_model.dart new file mode 100644 index 00000000..bf1eabc9 --- /dev/null +++ b/lib/modules/incident_module/incident_attachment_model.dart @@ -0,0 +1,24 @@ +class IncidentAttachments { + num? id; + num? incidentId; + String? attachmentName; + String? attachmentDescription; + + IncidentAttachments({this.id, this.attachmentName, this.incidentId, this.attachmentDescription}); + + IncidentAttachments.fromJson(dynamic json) { + id = json['id']; + incidentId = json['incidentId']; + attachmentName = json['attachmentName']; + attachmentDescription = json['attachmentDescription']; + } + + Map toJson() { + final map = {}; + map['id'] = id; + map['attachmentName'] = attachmentName; + map['incidentId'] = incidentId; + map['attachmentDescription'] = attachmentDescription; + return map; + } +} diff --git a/lib/modules/incident_module/incident_provider.dart b/lib/modules/incident_module/incident_provider.dart new file mode 100644 index 00000000..c4f24389 --- /dev/null +++ b/lib/modules/incident_module/incident_provider.dart @@ -0,0 +1,38 @@ +import 'package:flutter/cupertino.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/modules/loan_module/models/loan_request_model.dart'; +import 'dart:convert'; + +class IncidentProvider extends ChangeNotifier { + Future addIncidentRequest(Map body) async { + try { + Response response = await ApiManager.instance.post(URLs.addIncident, body: body); + if (response.statusCode >= 200 && response.statusCode < 300) { + String message = (jsonDecode(response.body)["message"] ?? ""); + Fluttertoast.showToast(msg: message ?? "", toastLength: Toast.LENGTH_LONG); + return true; + } + return false; + } catch (error) { + return false; + } + } + + bool isLoading = false; + + Future getIncidentById(int id) async { + LoanRequestModel? loanData; + try { + Response response = await ApiManager.instance.get(URLs.getIncidentById + "?incidentId=$id"); + if (response.statusCode >= 200 && response.statusCode < 300) { + loanData = LoanRequestModel.fromJson(json.decode(response.body)["data"]); + } + } catch (error) { + print(error); + } + return loanData; + } +} diff --git a/lib/modules/loan_module/provider/loan_provider.dart b/lib/modules/loan_module/provider/loan_provider.dart index b2a3a5e3..deccfe79 100644 --- a/lib/modules/loan_module/provider/loan_provider.dart +++ b/lib/modules/loan_module/provider/loan_provider.dart @@ -11,7 +11,7 @@ class LoanProvider extends ChangeNotifier { try { Response response = await ApiManager.instance.post(URLs.addLoan, body: body, showToast: false); if (response.statusCode >= 200 && response.statusCode < 300) { - String message = (jsonDecode(response.body)["data"] ?? "") + " " + (jsonDecode(response.body)["message"] ?? ""); + String message = (jsonDecode(response.body)["message"] ?? ""); Fluttertoast.showToast(msg: message ?? "", toastLength: Toast.LENGTH_LONG); return true; } From 6cf051e66e13f49e592d28cde73690bc549af497 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 9 Feb 2026 18:11:45 +0300 Subject: [PATCH 03/11] incident detail page addded. --- .../providers/api/all_requests_provider.dart | 4 + lib/modules/demo_module/demo_provider.dart | 2 +- .../create_incident_request_page.dart | 9 +- .../incident_module/incident_data_model.dart | 313 ++++++++++++++++++ .../incident_module/incident_detail_page.dart | 178 ++++++++++ .../incident_module/incident_item_view.dart | 117 +++++++ .../incident_module/incident_provider.dart | 10 +- .../loan_module/provider/loan_provider.dart | 2 +- .../my_request/all_requests_filter_page.dart | 14 +- .../my_request/my_requests_page.dart | 10 +- .../requests/request_paginated_listview.dart | 7 +- .../widgets/request_item_view_list.dart | 4 +- 12 files changed, 649 insertions(+), 21 deletions(-) create mode 100644 lib/modules/incident_module/incident_data_model.dart create mode 100644 lib/modules/incident_module/incident_detail_page.dart create mode 100644 lib/modules/incident_module/incident_item_view.dart diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index 86579328..b1bb12cf 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -172,6 +172,10 @@ class AllRequestsProvider extends ChangeNotifier { } // loan module + if (context.settingProvider.isUserFlowMedical && !context.userProvider.isNurse) { + list = [16]; + } + return list; } diff --git a/lib/modules/demo_module/demo_provider.dart b/lib/modules/demo_module/demo_provider.dart index 453df42b..18e64600 100644 --- a/lib/modules/demo_module/demo_provider.dart +++ b/lib/modules/demo_module/demo_provider.dart @@ -11,7 +11,7 @@ class DemoProvider extends ChangeNotifier { try { Response response = await ApiManager.instance.post(URLs.addLoan, body: body, showToast: false); if (response.statusCode >= 200 && response.statusCode < 300) { - String message = (jsonDecode(response.body)["message"] ?? ""); + String message = (jsonDecode(response.body)["data"] ?? "") + " " + (jsonDecode(response.body)["message"] ?? ""); Fluttertoast.showToast(msg: message ?? "", toastLength: Toast.LENGTH_LONG); return true; } diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 45a5eb86..606a7265 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -74,6 +74,10 @@ class _CreateIncidentRequestPageState extends State { @override void initState() { super.initState(); + payload["id"] = 0; + payload["isFromMobile"] = 0; + payload["requestorUserId"] = context.userProvider.user!.userID; + payload["requestorSiteId"] = context.userProvider.user!.clientId; } @override @@ -583,10 +587,7 @@ class _CreateIncidentRequestPageState extends State { } Utils.showLoading(context); IncidentProvider incidentProvider = Provider.of(context, listen: false); - payload["id"] = 0; - payload["isFromMobile"] = 0; - payload["requestorUserId"] = context.userProvider.user!.userID; - payload["requestorSiteId"] = context.userProvider.user!.clientId; + payload["incidentAttachments"] = attachmentList.map((v) => v.toJson()).toList(); bool isSuccess = await incidentProvider.addIncidentRequest(payload); Utils.hideLoading(context); diff --git a/lib/modules/incident_module/incident_data_model.dart b/lib/modules/incident_module/incident_data_model.dart new file mode 100644 index 00000000..6e5e46f1 --- /dev/null +++ b/lib/modules/incident_module/incident_data_model.dart @@ -0,0 +1,313 @@ +import 'package:test_sa/modules/incident_module/incident_attachment_model.dart'; + +class IncidentDataModel { + int? id; + int? reqNo; + String? reqCode; + int? incidentStatusId; + String? incidentStatusName; + int? incidentStatusValue; + int? incidentClassificationId; + String? incidentClassificationName; + int? incidentClassificationValue; + int? existingOvrTicketId; + String? existingOvrTicketName; + int? existingOvrTicketValue; + String? ovrTicketNumber; + String? incidentTitle; + String? incidentDescription; + int? assetId; + String? assetNumber; + String? oracleCode; + String? model; + String? manufacturer; + String? assetOrigin; + int? siteId; + String? siteName; + int? buildingId; + String? buildingName; + int? departmentId; + String? departmentName; + int? floorId; + String? floorName; + int? rootCauseId; + String? rootCauseName; + int? rootCauseValue; + String? otherRootCause; + String? comments; + int? personInvolvedId; + String? personInvolvedName; + int? personInvolvedValue; + int? genderId; + String? genderName; + int? personInvolvedEmployeeId; + String? personInvolvedEmployeeName; + String? assignedQAId; + String? assignedQAName; + String? assignedSecondaryQAId; + String? assignedSecondaryQAName; + String? qaLeaderId; + String? qaLeaderName; + String? correctiveAction; + int? escalateToOvrId; + String? escalateToOvrName; + int? immediateActionId; + String? immediateActionName; + int? clinicalOrNonClinicalId; + String? clinicalOrNonClinicalName; + String? recommendation; + String? requestorUserId; + String? requesterEmployeeId; + String? requesterEmployeeName; + String? requesterEmployeeEmail; + String? requesterPosition; + String? requesterContactNo; + String? requesterExtensionNo; + int? requestorSiteId; + String? requestorSiteName; + int? requestorDepartmentId; + String? requestorDepartmentName; + int? requestorFlowDepartmentId; + String? requestorFlowDepartmentName; + String? requestorFlowDepartmentShortCode; + String? approvalSignature; + String? occurrenceDate; + String? createdDate; + List? incidentAttachments; + + IncidentDataModel( + {this.id, + this.reqNo, + this.reqCode, + this.incidentStatusId, + this.incidentStatusName, + this.incidentStatusValue, + this.incidentClassificationId, + this.incidentClassificationName, + this.incidentClassificationValue, + this.existingOvrTicketId, + this.existingOvrTicketName, + this.existingOvrTicketValue, + this.ovrTicketNumber, + this.incidentTitle, + this.incidentDescription, + this.assetId, + this.assetNumber, + this.oracleCode, + this.model, + this.manufacturer, + this.assetOrigin, + this.siteId, + this.siteName, + this.buildingId, + this.buildingName, + this.departmentId, + this.departmentName, + this.floorId, + this.floorName, + this.rootCauseId, + this.rootCauseName, + this.rootCauseValue, + this.otherRootCause, + this.comments, + this.personInvolvedId, + this.personInvolvedName, + this.personInvolvedValue, + this.genderId, + this.genderName, + this.personInvolvedEmployeeId, + this.personInvolvedEmployeeName, + this.assignedQAId, + this.assignedQAName, + this.assignedSecondaryQAId, + this.assignedSecondaryQAName, + this.qaLeaderId, + this.qaLeaderName, + this.correctiveAction, + this.escalateToOvrId, + this.escalateToOvrName, + this.immediateActionId, + this.immediateActionName, + this.clinicalOrNonClinicalId, + this.clinicalOrNonClinicalName, + this.recommendation, + this.requestorUserId, + this.requesterEmployeeId, + this.requesterEmployeeName, + this.requesterEmployeeEmail, + this.requesterPosition, + this.requesterContactNo, + this.requesterExtensionNo, + this.requestorSiteId, + this.requestorSiteName, + this.requestorDepartmentId, + this.requestorDepartmentName, + this.requestorFlowDepartmentId, + this.requestorFlowDepartmentName, + this.requestorFlowDepartmentShortCode, + this.approvalSignature, + this.occurrenceDate, + this.createdDate, + this.incidentAttachments}); + + IncidentDataModel.fromJson(Map json) { + id = json['id']; + reqNo = json['reqNo']; + reqCode = json['reqCode']; + incidentStatusId = json['incidentStatusId']; + incidentStatusName = json['incidentStatusName']; + incidentStatusValue = json['incidentStatusValue']; + incidentClassificationId = json['incidentClassificationId']; + incidentClassificationName = json['incidentClassificationName']; + incidentClassificationValue = json['incidentClassificationValue']; + existingOvrTicketId = json['existingOvrTicketId']; + existingOvrTicketName = json['existingOvrTicketName']; + existingOvrTicketValue = json['existingOvrTicketValue']; + ovrTicketNumber = json['ovrTicketNumber']; + incidentTitle = json['incidentTitle']; + incidentDescription = json['incidentDescription']; + assetId = json['assetId']; + assetNumber = json['assetNumber']; + oracleCode = json['oracleCode']; + model = json['model']; + manufacturer = json['manufacturer']; + assetOrigin = json['assetOrigin']; + siteId = json['siteId']; + siteName = json['siteName']; + buildingId = json['buildingId']; + buildingName = json['buildingName']; + departmentId = json['departmentId']; + departmentName = json['departmentName']; + floorId = json['floorId']; + floorName = json['floorName']; + rootCauseId = json['rootCauseId']; + rootCauseName = json['rootCauseName']; + rootCauseValue = json['rootCauseValue']; + otherRootCause = json['otherRootCause']; + comments = json['comments']; + personInvolvedId = json['personInvolvedId']; + personInvolvedName = json['personInvolvedName']; + personInvolvedValue = json['personInvolvedValue']; + genderId = json['genderId']; + genderName = json['genderName']; + personInvolvedEmployeeId = json['personInvolvedEmployeeId']; + personInvolvedEmployeeName = json['personInvolvedEmployeeName']; + assignedQAId = json['assignedQAId']; + assignedQAName = json['assignedQAName']; + assignedSecondaryQAId = json['assignedSecondaryQAId']; + assignedSecondaryQAName = json['assignedSecondaryQAName']; + qaLeaderId = json['qaLeaderId']; + qaLeaderName = json['qaLeaderName']; + correctiveAction = json['correctiveAction']; + escalateToOvrId = json['escalateToOvrId']; + escalateToOvrName = json['escalateToOvrName']; + immediateActionId = json['immediateActionId']; + immediateActionName = json['immediateActionName']; + clinicalOrNonClinicalId = json['clinicalOrNonClinicalId']; + clinicalOrNonClinicalName = json['clinicalOrNonClinicalName']; + recommendation = json['recommendation']; + requestorUserId = json['requestorUserId']; + requesterEmployeeId = json['requesterEmployeeId']; + requesterEmployeeName = json['requesterEmployeeName']; + requesterEmployeeEmail = json['requesterEmployeeEmail']; + requesterPosition = json['requesterPosition']; + requesterContactNo = json['requesterContactNo']; + requesterExtensionNo = json['requesterExtensionNo']; + requestorSiteId = json['requestorSiteId']; + requestorSiteName = json['requestorSiteName']; + requestorDepartmentId = json['requestorDepartmentId']; + requestorDepartmentName = json['requestorDepartmentName']; + requestorFlowDepartmentId = json['requestorFlowDepartmentId']; + requestorFlowDepartmentName = json['requestorFlowDepartmentName']; + requestorFlowDepartmentShortCode = json['requestorFlowDepartmentShortCode']; + approvalSignature = json['approvalSignature']; + occurrenceDate = json['occurrenceDate']; + createdDate = json['createdDate']; + if (json['incidentAttachments'] != null) { + incidentAttachments = []; + json['incidentAttachments'].forEach((v) { + incidentAttachments!.add(IncidentAttachments.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['reqNo'] = this.reqNo; + data['reqCode'] = this.reqCode; + data['incidentStatusId'] = this.incidentStatusId; + data['incidentStatusName'] = this.incidentStatusName; + data['incidentStatusValue'] = this.incidentStatusValue; + data['incidentClassificationId'] = this.incidentClassificationId; + data['incidentClassificationName'] = this.incidentClassificationName; + data['incidentClassificationValue'] = this.incidentClassificationValue; + data['existingOvrTicketId'] = this.existingOvrTicketId; + data['existingOvrTicketName'] = this.existingOvrTicketName; + data['existingOvrTicketValue'] = this.existingOvrTicketValue; + data['ovrTicketNumber'] = this.ovrTicketNumber; + data['incidentTitle'] = this.incidentTitle; + data['incidentDescription'] = this.incidentDescription; + data['assetId'] = this.assetId; + data['assetNumber'] = this.assetNumber; + data['oracleCode'] = this.oracleCode; + data['model'] = this.model; + data['manufacturer'] = this.manufacturer; + data['assetOrigin'] = this.assetOrigin; + data['siteId'] = this.siteId; + data['siteName'] = this.siteName; + data['buildingId'] = this.buildingId; + data['buildingName'] = this.buildingName; + data['departmentId'] = this.departmentId; + data['departmentName'] = this.departmentName; + data['floorId'] = this.floorId; + data['floorName'] = this.floorName; + data['rootCauseId'] = this.rootCauseId; + data['rootCauseName'] = this.rootCauseName; + data['rootCauseValue'] = this.rootCauseValue; + data['otherRootCause'] = this.otherRootCause; + data['comments'] = this.comments; + data['personInvolvedId'] = this.personInvolvedId; + data['personInvolvedName'] = this.personInvolvedName; + data['personInvolvedValue'] = this.personInvolvedValue; + data['genderId'] = this.genderId; + data['genderName'] = this.genderName; + data['personInvolvedEmployeeId'] = this.personInvolvedEmployeeId; + data['personInvolvedEmployeeName'] = this.personInvolvedEmployeeName; + data['assignedQAId'] = this.assignedQAId; + data['assignedQAName'] = this.assignedQAName; + data['assignedSecondaryQAId'] = this.assignedSecondaryQAId; + data['assignedSecondaryQAName'] = this.assignedSecondaryQAName; + data['qaLeaderId'] = this.qaLeaderId; + data['qaLeaderName'] = this.qaLeaderName; + data['correctiveAction'] = this.correctiveAction; + data['escalateToOvrId'] = this.escalateToOvrId; + data['escalateToOvrName'] = this.escalateToOvrName; + data['immediateActionId'] = this.immediateActionId; + data['immediateActionName'] = this.immediateActionName; + data['clinicalOrNonClinicalId'] = this.clinicalOrNonClinicalId; + data['clinicalOrNonClinicalName'] = this.clinicalOrNonClinicalName; + data['recommendation'] = this.recommendation; + data['requestorUserId'] = this.requestorUserId; + data['requesterEmployeeId'] = this.requesterEmployeeId; + data['requesterEmployeeName'] = this.requesterEmployeeName; + data['requesterEmployeeEmail'] = this.requesterEmployeeEmail; + data['requesterPosition'] = this.requesterPosition; + data['requesterContactNo'] = this.requesterContactNo; + data['requesterExtensionNo'] = this.requesterExtensionNo; + data['requestorSiteId'] = this.requestorSiteId; + data['requestorSiteName'] = this.requestorSiteName; + data['requestorDepartmentId'] = this.requestorDepartmentId; + data['requestorDepartmentName'] = this.requestorDepartmentName; + data['requestorFlowDepartmentId'] = this.requestorFlowDepartmentId; + data['requestorFlowDepartmentName'] = this.requestorFlowDepartmentName; + data['requestorFlowDepartmentShortCode'] = this.requestorFlowDepartmentShortCode; + data['approvalSignature'] = this.approvalSignature; + data['occurrenceDate'] = this.occurrenceDate; + data['createdDate'] = this.createdDate; + if (this.incidentAttachments != null) { + data['incidentAttachments'] = this.incidentAttachments!.map((v) => v.toJson()).toList(); + } + return data; + } +} diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart new file mode 100644 index 00000000..ea9f9b1f --- /dev/null +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -0,0 +1,178 @@ +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/incident_module/incident_attachment_model.dart'; +import 'package:test_sa/modules/incident_module/incident_data_model.dart'; +import 'package:test_sa/modules/incident_module/incident_provider.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/modules/loan_module/models/loan_attachment_model.dart'; +import 'package:test_sa/modules/loan_module/models/loan_request_model.dart'; +import 'package:test_sa/modules/loan_module/provider/loan_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'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +class IncidentDetailPage extends StatelessWidget { + static const String id = "/incident-detail-page"; + final int incidentId; + + IncidentDetailPage({Key? key, required this.incidentId}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const DefaultAppBar(title: "Request Details"), + body: SafeArea( + child: FutureBuilder( + future: Provider.of(context, listen: false).getIncidentById(97), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator(color: AppColor.primary10).center; + if (snapshot.data == null) return const NoDataFound().center; + + List allAttachments = snapshot.data!.incidentAttachments!; + + return ListView( + padding: const EdgeInsets.all(16), + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: snapshot.data!.incidentStatusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.incidentStatusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.incidentStatusName!), + ), + 1.width.expanded, + Text( + snapshot.data!.createdDate?.toServiceRequestCardFormat ?? "-", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], + ), + 12.height, + ...requesterDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...requestDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...assetDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...personInvolvedDetails(context, snapshot.data!), + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), + ], + ], + ).toShadowContainer(context, padding: 12), + ], + ); + }), + )); + } + + List requestDetails(BuildContext context, IncidentDataModel incidentData) { + return [ + Text("Request Details", style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), + 'Incident No: ${incidentData.reqCode ?? "-"}'.bodyText(context), + 'Incident Type: ${incidentData.incidentClassificationName ?? "-"}'.bodyText(context), + 'Incident Title: ${incidentData.incidentTitle ?? "-"}'.bodyText(context), + 'Incident Description: ${incidentData.incidentDescription ?? "-"}'.bodyText(context), + 'Exist OVR: ${incidentData.existingOvrTicketName ?? "-"}'.bodyText(context), + 'Site: ${incidentData.siteName ?? "-"}'.bodyText(context), + 'Building: ${incidentData.buildingName ?? "-"}'.bodyText(context), + 'Floor: ${incidentData.floorName ?? "-"}'.bodyText(context), + 'Department: ${incidentData.departmentName ?? "-"}'.bodyText(context), + 'Occurrence Date: ${incidentData.occurrenceDate?.toServiceRequestDetailsFormat ?? "-"}'.bodyText(context), + 'Root Cause: ${incidentData.rootCauseName ?? "-"}'.bodyText(context), + if (incidentData.rootCauseValue == 5) 'Root reason: ${incidentData.otherRootCause ?? "-"}'.bodyText(context), + 'Comments: ${incidentData.comments ?? "-"}'.bodyText(context), + ]; + } + + List personInvolvedDetails(BuildContext context, IncidentDataModel incidentData) { + return [ + Text("Person Involved Information", style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), + 'Person Involved: ${incidentData.personInvolvedName ?? "-"}'.bodyText(context), + 'Gender: ${incidentData.genderName ?? "-"}'.bodyText(context), + 'Person Involved ID: ${incidentData.personInvolvedEmployeeId ?? "-"}'.bodyText(context), + 'Person Involved Name: ${incidentData.personInvolvedEmployeeName ?? "-"}'.bodyText(context), + ]; + } + + List assetDetails(BuildContext context, IncidentDataModel loanData) { + return [ + Text("Asset Information", style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), + '${context.translation.assetName}: ${loanData.assetOrigin ?? "-"}'.bodyText(context), + '${context.translation.assetNo}: ${loanData.assetNumber ?? "-"}'.bodyText(context), + // '${context.translation.serialNumber}: ${loanData.assetSerialNumber ?? "-"}'.bodyText(context), + '${context.translation.manufacture}: ${loanData.manufacturer ?? "-"}'.bodyText(context), + '${context.translation.model}: ${loanData.model ?? "-"}'.bodyText(context), + ]; + } + + List requesterDetails(BuildContext context, IncidentDataModel incidentData) { + return [ + Text("Requester Information", style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), + 'Name: ${incidentData.requesterEmployeeName ?? "-"}'.bodyText(context), + 'Email: ${incidentData.requesterEmployeeEmail ?? "-"}'.bodyText(context), + 'Contact: ${incidentData.requesterContactNo ?? "-"}'.bodyText(context), + 'Extension: ${incidentData.requesterExtensionNo ?? "-"}'.bodyText(context), + 'Site: ${incidentData.requestorSiteName ?? "-"}'.bodyText(context), + 'Department: ${incidentData.requestorFlowDepartmentName ?? "-"}'.bodyText(context), + ]; + } + + 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(BuildContext context, {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/incident_module/incident_item_view.dart b/lib/modules/incident_module/incident_item_view.dart new file mode 100644 index 00000000..ff4db6fa --- /dev/null +++ b/lib/modules/incident_module/incident_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/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +import 'incident_detail_page.dart'; + +class IncidentItemView extends StatelessWidget { + final Data? requestData; + final RequestsDetails? requestDetails; + final bool showShadow; + + const IncidentItemView({Key? key, this.requestData, this.requestDetails, this.showShadow = true}) : super(key: key); + + @override + Widget build(BuildContext context) { + 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 ?? "Loan Request").heading5(context), + infoWidget(label: context.translation.requestType, value: requestData?.requestTypeName, context: context), + infoWidget(label: "Incident No", value: requestData?.requestNo, context: context), + // infoWidget(label: context.translation.site, value: requestData?., 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) => IncidentDetailPage(incidentId: 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 ?? "Loan Request").heading5(context), + 8.height, + infoWidget(label: context.translation.requestType, value: requestDetails!.requestType, context: context), + infoWidget(label: "Incident No", value: requestDetails!.requestNo, context: context), + 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) => IncidentDetailPage(incidentId: 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/incident_module/incident_provider.dart b/lib/modules/incident_module/incident_provider.dart index c4f24389..bb692016 100644 --- a/lib/modules/incident_module/incident_provider.dart +++ b/lib/modules/incident_module/incident_provider.dart @@ -6,12 +6,14 @@ import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/modules/loan_module/models/loan_request_model.dart'; import 'dart:convert'; +import 'incident_data_model.dart'; + class IncidentProvider extends ChangeNotifier { Future addIncidentRequest(Map body) async { try { Response response = await ApiManager.instance.post(URLs.addIncident, body: body); if (response.statusCode >= 200 && response.statusCode < 300) { - String message = (jsonDecode(response.body)["message"] ?? ""); + String message = (jsonDecode(response.body)["data"] ?? "") + " " + (jsonDecode(response.body)["message"] ?? ""); Fluttertoast.showToast(msg: message ?? "", toastLength: Toast.LENGTH_LONG); return true; } @@ -23,12 +25,12 @@ class IncidentProvider extends ChangeNotifier { bool isLoading = false; - Future getIncidentById(int id) async { - LoanRequestModel? loanData; + Future getIncidentById(int id) async { + IncidentDataModel? loanData; try { Response response = await ApiManager.instance.get(URLs.getIncidentById + "?incidentId=$id"); if (response.statusCode >= 200 && response.statusCode < 300) { - loanData = LoanRequestModel.fromJson(json.decode(response.body)["data"]); + loanData = IncidentDataModel.fromJson(json.decode(response.body)["data"]); } } catch (error) { print(error); diff --git a/lib/modules/loan_module/provider/loan_provider.dart b/lib/modules/loan_module/provider/loan_provider.dart index deccfe79..b2a3a5e3 100644 --- a/lib/modules/loan_module/provider/loan_provider.dart +++ b/lib/modules/loan_module/provider/loan_provider.dart @@ -11,7 +11,7 @@ class LoanProvider extends ChangeNotifier { try { Response response = await ApiManager.instance.post(URLs.addLoan, body: body, showToast: false); if (response.statusCode >= 200 && response.statusCode < 300) { - String message = (jsonDecode(response.body)["message"] ?? ""); + String message = (jsonDecode(response.body)["data"] ?? "") + " " + (jsonDecode(response.body)["message"] ?? ""); Fluttertoast.showToast(msg: message ?? "", toastLength: Toast.LENGTH_LONG); return true; } 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 35253c86..a5226b80 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 @@ -79,9 +79,9 @@ class _AllRequestsFilterPageState extends State { if (isEngineer) { types[context.translation.recurrentWo] = 5; - if (context.settingProvider.isUserFlowMedical) types["Equipment Internal Audit".addTranslation] = 10; - if (context.settingProvider.isUserFlowMedical) types["System Internal Audit".addTranslation] = 11; - if (context.settingProvider.isUserFlowMedical) types["Medical Gas Inspection".addTranslation] = 11; + if (context.settingProvider.isUserFlowMedical) types["Equipment Internal Audit".addTranslation] = 10; + if (context.settingProvider.isUserFlowMedical) types["System Internal Audit".addTranslation] = 11; + if (context.settingProvider.isUserFlowMedical) types["Medical Gas Inspection".addTranslation] = 11; } if (context.settingProvider.isUserFlowMedical && isEngineer) { @@ -104,8 +104,8 @@ class _AllRequestsFilterPageState extends State { // } if (context.userProvider.isQualityUser) { if (context.settingProvider.isUserFlowMedical) types = {'Recall and Alert': 7}; - if (context.settingProvider.isUserFlowMedical) types['Equipment Internal Audit'] = 10; - if (context.settingProvider.isUserFlowMedical) types['System Internal Audit'] = 11; + if (context.settingProvider.isUserFlowMedical) types['Equipment Internal Audit'] = 10; + if (context.settingProvider.isUserFlowMedical) types['System Internal Audit'] = 11; } if (context.userProvider.isCommissioningEngineer) { ///TODO need to check what other requests type needed here. @@ -114,6 +114,10 @@ class _AllRequestsFilterPageState extends State { }; } + if (context.settingProvider.isUserFlowMedical && !context.userProvider.isNurse) { + types = {'Incidents': 16}; + } + final statuses = { "All WO": 0, context.translation.open: 1, 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 e28dca9c..14a8530f 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 @@ -66,7 +66,7 @@ class _MyRequestsPageState extends State { // if (context.userProvider.user!.type == UsersTypes.normal_user) { // requestsList.add(Request(9, 'TRAF')); // } - if (context.userProvider.isEngineer &&context.settingProvider.isUserFlowMedical) { + if (context.userProvider.isEngineer && context.settingProvider.isUserFlowMedical) { requestsList.add(Request(10, 'Equipment Internal Audit')); requestsList.add(Request(11, 'System Internal Audit')); //Need to be added for new user role commissioning engineer @@ -93,11 +93,15 @@ class _MyRequestsPageState extends State { requestsList = [ Request(null, context.translation.allWorkOrder), if (context.settingProvider.isUserFlowMedical) Request(7, 'Recall and Alert'), - if (context.settingProvider.isUserFlowMedical) Request(10, 'Equipment Internal Audit'), - if (context.settingProvider.isUserFlowMedical) Request(11, 'System Internal Audit'), + if (context.settingProvider.isUserFlowMedical) Request(10, 'Equipment Internal Audit'), + if (context.settingProvider.isUserFlowMedical) Request(11, 'System Internal Audit'), ]; } + if (context.settingProvider.isUserFlowMedical && !context.userProvider.isNurse) { + requestsList.add(Request(16, "Incidents")); + } + _provider = Provider.of(context, listen: false); _provider!.reset(); WidgetsBinding.instance.addPostFrameCallback((_) { 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 f4d83656..ab6c9a78 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 @@ -2,6 +2,7 @@ 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/asset_delivery_module/pages/asset_delivery_item_view.dart'; +import 'package:test_sa/modules/incident_module/incident_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/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'; @@ -77,10 +78,12 @@ class RequestPaginatedListview extends StatelessWidget { return SystemInternalAuditItemView(requestData: request); case 12: return LoanEquipmentItemView(requestData: request); - case 13: + case 13: return AssetDeliveryItemView(requestData: request); - case 14: + case 14: return MedicalGasInspectionItemView(requestData: request); + case 16: + return IncidentItemView(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 10339da7..4ae0167d 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 @@ -7,6 +7,7 @@ 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/asset_delivery_module/pages/asset_delivery_item_view.dart'; +import 'package:test_sa/modules/incident_module/incident_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/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'; @@ -77,7 +78,8 @@ class RequestItemViewList extends StatelessWidget { return AssetDeliveryItemView(requestDetails: list[index]); case 14: return MedicalGasInspectionItemView(requestDetails: list[index]); - + case 16: + return IncidentItemView(requestDetails: list[index]); default: Container( height: 100, From 6e1b63cef8a3191a72e13deddb5f54b41748b82b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 10 Feb 2026 09:56:28 +0300 Subject: [PATCH 04/11] improvements. --- lib/modules/incident_module/create_incident_request_page.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 606a7265..a49a4922 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -118,6 +118,10 @@ class _CreateIncidentRequestPageState extends State { payload["model"] = asset.modelDefinition?.modelName; payload["manufacturer"] = asset.modelDefinition?.manufacturerId; payload["assetOrigin"] = asset.modelDefinition?.assetName; + payload["siteId"] = asset.site?.id; + payload["buildingId"] = asset.building?.id; + payload["floorId"] = asset.floor?.id; + payload["departmentId"] = asset.department?.id; setState(() {}); }, ) From 2349925ed148836f1e8cea3eee5d1e6a0ff99636 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 10 Feb 2026 17:47:39 +0300 Subject: [PATCH 05/11] information dialog added. --- lib/extensions/context_extension.dart | 6 + .../create_incident_request_page.dart | 39 ++++ .../single_item_drop_down_menu.dart | 187 +++++++++--------- .../swipe_module/dialoge/info_dialog.dart | 86 ++++++++ 4 files changed, 228 insertions(+), 90 deletions(-) create mode 100644 lib/new_views/swipe_module/dialoge/info_dialog.dart diff --git a/lib/extensions/context_extension.dart b/lib/extensions/context_extension.dart index b7bb652a..d214cf98 100644 --- a/lib/extensions/context_extension.dart +++ b/lib/extensions/context_extension.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/confirm_dialog.dart'; +import 'package:test_sa/new_views/swipe_module/dialoge/info_dialog.dart'; import '../controllers/providers/settings/setting_provider.dart'; @@ -36,6 +37,11 @@ extension BuildContextExtension on BuildContext { builder: (BuildContext cxt) => ConfirmDialog(message: message, onTap: onTap, title: title, okTitle: okTitle), ); + void showInfoDialog(String message, {String? title, VoidCallback? onTap, String? okTitle, List content = const []}) => showDialog( + context: this, + builder: (BuildContext cxt) => InfoDialog(message: message, onTap: onTap, title: title, okTitle: okTitle, content: content), + ); + Future showBottomSheet(Widget childWidget, {bool? isDismissible, String? title}) => showModalBottomSheet( context: this, useSafeArea: true, diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index a49a4922..71d6e3a8 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -35,6 +35,7 @@ import 'package:test_sa/views/widgets/equipment/asset_picker.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import '../../models/new_models/department.dart'; +import '../../new_views/swipe_module/dialoge/info_dialog.dart'; import 'incident_attachment_model.dart'; class CreateIncidentRequestPage extends StatefulWidget { @@ -330,6 +331,14 @@ class _CreateIncidentRequestPageState extends State { if (value == null) return "Mandatory"; return null; }, + infoCallback: () { + context.showInfoDialog("Select the category that best describes the incident:", content: [ + InfoContent(title: "Medical Equipment", message: "Malfunction, failure, or issue involving a device."), + InfoContent(title: "EHS (Environment, Health, Safety)", message: "Incidents related to safety hazards, spills, injuries."), + InfoContent(title: "Late Delivery", message: "Delayed receipt of medical equipment, materials, or supplies."), + InfoContent(title: "Mis-conduct", message: "FLOW staff behavior that violates rules, ethics, or procedures."), + ]); + }, backgroundColor: AppColor.fieldBgColor(context), showAsBottomSheet: true, onSelect: (value) { @@ -385,6 +394,9 @@ class _CreateIncidentRequestPageState extends State { if ((value ?? "").isEmpty) return "Mandatory"; return null; }, + suffixIcon: const Icon(Icons.info_outline_rounded, color: AppColor.neutral120).paddingOnly(end: 8).onPress(() { + context.showInfoDialog("Brief explanation of the incident, e.g., “Damaged patient monitor screen“"); + }), style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), @@ -403,6 +415,10 @@ class _CreateIncidentRequestPageState extends State { if ((value ?? "").isEmpty) return "Mandatory"; return null; }, + suffixIcon: const Icon(Icons.info_outline_rounded, color: AppColor.neutral120).paddingOnly(end: 8).onPress(() { + context.showInfoDialog( + "Provide a clear and factual description of the incident, including how the issue was identified. Ex. “The ECG cable connector was damaged due to continuos forcfull disconnetionnby end-users. Multiple bent pins were noted during inspection after repeated complains of poor signal quality“"); + }), style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), @@ -430,6 +446,19 @@ class _CreateIncidentRequestPageState extends State { if (value == null) return "Mandatory"; return null; }, + infoCallback: () { + context.showInfoDialog("Select the primary cause that best explains why the incident occurred. If “Other“ is selected, specify the details in the text box provided.", content: [ + InfoContent( + title: "Misuse", + message: "Clear, improper use of the device by the end-user despite available knowledge or instructions. Misuse implies that correct handling was possible but not followed."), + InfoContent(title: "Lack of Awareness", message: "End-user was unaware of proper handling or procedures due to missing information or communication."), + InfoContent(title: "Insufficient Training", message: "Formal training on the device was not provided, incomplete, or outdated, leading to misuse."), + InfoContent( + title: "Utility Failure", + message: "A utility issue that disrupted the medical device operation, degrade, performance, or create unsafe conditions. such as (power outage, HVAC, or environment)."), + InfoContent(title: "Other", message: "Any cause that does not fit into the categories above (please specify)."), + ]); + }, backgroundColor: AppColor.fieldBgColor(context), showAsBottomSheet: true, onSelect: (value) { @@ -469,6 +498,9 @@ class _CreateIncidentRequestPageState extends State { if (value == null) return "Mandatory"; return null; }, + infoCallback: () { + context.showInfoDialog("Ensure to provide information of person in Charge (e.g., Charge Nurse, Unit Team Leader), not a general staff member.", content: []); + }, backgroundColor: AppColor.fieldBgColor(context), showAsBottomSheet: true, onSelect: (value) { @@ -502,6 +534,10 @@ class _CreateIncidentRequestPageState extends State { if (involvedPerson?.value == 3 && (value ?? "").isEmpty) return "Mandatory"; return null; }, + suffixIcon: const Icon(Icons.info_outline_rounded, color: AppColor.neutral120).paddingOnly(end: 8).onPress(() { + context.showInfoDialog( + "Required if the person is an Employee. Ensure the correct Employee ID of the Person in Charge (e.g., Charge Nurse, Unit Team Leader) is provided, not a general staff member. (Ex. 34567)"); + }), style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), @@ -520,6 +556,9 @@ class _CreateIncidentRequestPageState extends State { if (involvedPerson?.value == 3 && (value ?? "").isEmpty) return "Mandatory"; return null; }, + suffixIcon: const Icon(Icons.info_outline_rounded, color: AppColor.neutral120).paddingOnly(end: 8).onPress(() { + context.showInfoDialog("State the end-user Employee/Patient Full name. Required if the person is an Employee. (Ex. Mary-Anne Joseph)"); + }), style: Theme.of(context).textTheme.titleMedium, backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), diff --git a/lib/new_views/common_widgets/single_item_drop_down_menu.dart b/lib/new_views/common_widgets/single_item_drop_down_menu.dart index ce7f7838..3774a4eb 100644 --- a/lib/new_views/common_widgets/single_item_drop_down_menu.dart +++ b/lib/new_views/common_widgets/single_item_drop_down_menu.dart @@ -31,6 +31,7 @@ class SingleItemDropDownMenu exte final Color? titleTextColor; // Now nullable final bool? loading; // Now nullable final int? requestById; + final VoidCallback? infoCallback; final String? Function(T?)? validator; final AutovalidateMode? autovalidateMode; final DropDownMatchType matchBy; @@ -46,6 +47,7 @@ class SingleItemDropDownMenu exte this.disableValue, this.enabled = true, this.height, + this.infoCallback, this.showAsBottomSheet = false, this.showAsFullScreenDialog = true, this.staticData, // Provide a default empty list @@ -187,7 +189,7 @@ class _SingleItemDropDownMenuState( - value: _selectedItem, - iconSize: 24, - isDense: true, - icon: const SizedBox.shrink(), - dropdownColor: AppColor.neutral100, - elevation: 0, - isExpanded: true, - hint: Text( - isEmpty || widget.enabled == false ? context.translation.noDataFound : context.translation.select, - style: Theme.of(context).textTheme.bodyLarge, - ), - style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12), - underline: const SizedBox.shrink(), - onChanged: (widget.enabled == false || widget.showAsBottomSheet || widget.showAsFullScreenDialog) - ? null - : (T? newValue) { - // Now accepts nullable values - final isNull = newValue?.identifier == "-1"; - setState(() { - _selectedItem = isNull ? null : newValue; - }); - fieldState.didChange(isNull ? null : newValue); - widget.onSelect!(isNull ? null : newValue); - }, - items: ((X == NullableLoadingProvider) ? widget.staticData : provider?.items as List)?.map>((value) { - return DropdownMenuItem( - value: value, - child: Text( - value.name ?? "", // Null-aware operator for value.name - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500, color: context.isDark ? AppColor.white10 : AppColor.black10), + if (widget.enabled) const PositionedDirectional(end: 0, child: Icon(Icons.keyboard_arrow_down_rounded)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.title, + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: context.isDark ? null : widget.titleTextColor ?? AppColor.neutral20, fontWeight: FontWeight.w500), + ), + DropdownButton( + value: _selectedItem, + iconSize: 24, + isDense: true, + icon: const SizedBox.shrink(), + dropdownColor: AppColor.neutral100, + elevation: 0, + isExpanded: true, + hint: Text( + isEmpty || widget.enabled == false ? context.translation.noDataFound : context.translation.select, + style: Theme.of(context).textTheme.bodyLarge, ), - ); - }).toList(), - ), + style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12), + underline: const SizedBox.shrink(), + onChanged: (widget.enabled == false || widget.showAsBottomSheet || widget.showAsFullScreenDialog) + ? null + : (T? newValue) { + // Now accepts nullable values + final isNull = newValue?.identifier == "-1"; + setState(() { + _selectedItem = isNull ? null : newValue; + }); + fieldState.didChange(isNull ? null : newValue); + widget.onSelect!(isNull ? null : newValue); + }, + items: ((X == NullableLoadingProvider) ? widget.staticData : provider?.items as List)?.map>((value) { + return DropdownMenuItem( + value: value, + child: Text( + value.name ?? "", // Null-aware operator for value.name + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500, color: context.isDark ? AppColor.white10 : AppColor.black10), + ), + ); + }).toList(), + ), + ], + ).onPress(widget.enabled && widget.showAsFullScreenDialog + ? () { + openDialog(fieldState); + } + : widget.enabled + ? (widget.showAsBottomSheet + ? () async { + final selectedT = await showModalBottomSheet( + // Specify return type + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + clipBehavior: Clip.antiAliasWithSaveLayer, + builder: (BuildContext context) => SelectionBottomSheet( + // Specify generic type + items: ((X == NullableLoadingProvider) ? widget.staticData : provider?.items as List) ?? [], + // Provide default empty list if null + selectedItem: _selectedItem, + title: widget.title, + showCancel: widget.showCancel, + onSelect: (selectedT) { + setState(() { + _selectedItem = selectedT; + }); + fieldState.didChange(selectedT); + widget.onSelect!(selectedT); + }, + builderString: (emp) => emp?.name ?? "", // Null-aware operator for emp.name + ), + ); + // if (selectedT != null) { + // setState(() { + // _selectedItem = selectedT; + // }); + // widget.onSelect!(selectedT); // Non-null assertion after null check + // } + } + : null) + : null), ], - ).onPress(widget.enabled && widget.showAsFullScreenDialog - ? () { - openDialog(fieldState); - } - : widget.enabled - ? (widget.showAsBottomSheet - ? () async { - final selectedT = await showModalBottomSheet( - // Specify return type - context: context, - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - clipBehavior: Clip.antiAliasWithSaveLayer, - builder: (BuildContext context) => SelectionBottomSheet( - // Specify generic type - items: ((X == NullableLoadingProvider) ? widget.staticData : provider?.items as List) ?? [], - // Provide default empty list if null - selectedItem: _selectedItem, - title: widget.title, - showCancel: widget.showCancel, - onSelect: (selectedT) { - setState(() { - _selectedItem = selectedT; - }); - fieldState.didChange(selectedT); - widget.onSelect!(selectedT); - }, - builderString: (emp) => emp?.name ?? "", // Null-aware operator for emp.name - ), - ); - // if (selectedT != null) { - // setState(() { - // _selectedItem = selectedT; - // }); - // widget.onSelect!(selectedT); // Non-null assertion after null check - // } - } - : null) - : null), + ).expanded, + if (widget.infoCallback != null) const Icon(Icons.info_outline_rounded, color: AppColor.neutral120).onPress(widget.infoCallback), ], ), ), diff --git a/lib/new_views/swipe_module/dialoge/info_dialog.dart b/lib/new_views/swipe_module/dialoge/info_dialog.dart new file mode 100644 index 00000000..90057706 --- /dev/null +++ b/lib/new_views/swipe_module/dialoge/info_dialog.dart @@ -0,0 +1,86 @@ +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/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; + +class InfoContent { + final String? title; + final String? message; + + InfoContent({this.title, this.message}); +} + +class InfoDialog extends StatelessWidget { + final String? title; + final String? message; + final List content; + + final String? okTitle; + final VoidCallback? onTap; + final VoidCallback? onCloseTap; + + const InfoDialog({Key? key, this.title, this.message, this.content = const [], this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: AppColor.background(context), + shape: const RoundedRectangleBorder(), + insetPadding: const EdgeInsets.only(left: 21, right: 21), + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 20, top: 18, bottom: 28), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + title ?? "Information", + style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), + ).paddingOnly(top: 16), + ), + IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.close), + color: AppColor.iconColor(context), + constraints: const BoxConstraints(), + onPressed: () => onCloseTap ?? Navigator.pop(context), + // onPressed: () => Navigator.pop(context), + ) + ], + ), + message != null ? message!.bodyText(context).custom(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20) : const SizedBox(), + 8.height, + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (cxt, index) => RichText( + text: TextSpan( + text: "${content[index].title}: ", + style: AppTextStyles.heading6.copyWith(fontWeight: FontWeight.w600, color: AppColor.textColor(context)), + children: [ + TextSpan(text: content[index].message, style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20)), + ], + ), + ), + separatorBuilder: (cxt, index) => 6.height, + itemCount: content.length), + 24.height, + AppFilledButton( + label: okTitle ?? "OK", + onPressed: onTap ?? () => Navigator.pop(context), + textColor: Colors.white, + //color: Ap.green, + ), + ], + ), + ), + ); + } +} From 0a06edd883cddfdedfdd301d7bf1d5b470b0d892 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 12 Feb 2026 08:55:26 +0300 Subject: [PATCH 06/11] improvements --- lib/controllers/api_routes/urls.dart | 1 + lib/main.dart | 2 ++ .../create_incident_request_page.dart | 5 +-- .../flow_medical_department_provider.dart | 32 +++++++++++++++++++ .../incident_module/incident_detail_page.dart | 2 +- .../incident_module/incident_item_view.dart | 1 + 6 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 lib/modules/incident_module/flow_medical_department_provider.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 10ea73dd..42facf90 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -406,6 +406,7 @@ class URLs { static get getLoanPeriod => "$_baseUrl/Lookups/GetLookup?lookupEnum=3051"; static get getMedDepartmentBasedOnSite => "$_baseUrl/TRAFDataSource/GetDepartmentBasedOnSite"; + static get getFlowMedicalDepartments => "$_baseUrl/FlowDepartments/All"; static get getLoanById => '$_baseUrl/Loan/GetLoanById'; static get getIncidentById => '$_baseUrl/Incident/GetIncidentById'; diff --git a/lib/main.dart b/lib/main.dart index 1d64b423..957e9211 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,6 +40,7 @@ import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; import 'package:test_sa/modules/demo_module/create_demo_request_page.dart'; import 'package:test_sa/modules/demo_module/demo_period_lookup_provider.dart'; import 'package:test_sa/modules/demo_module/demo_provider.dart'; +import 'package:test_sa/modules/incident_module/flow_medical_department_provider.dart'; import 'package:test_sa/modules/incident_module/incident_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'; @@ -334,6 +335,7 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => LoanProvider()), ChangeNotifierProvider(create: (_) => LoanPeriodProvider()), ChangeNotifierProvider(create: (_) => MedicalDepartmentProvider()), + ChangeNotifierProvider(create: (_) => FlowMedicalDepartmentProvider()), ChangeNotifierProvider(create: (_) => AssetDeliveryProvider()), ChangeNotifierProvider(create: (_) => CostCenterProvider()), ChangeNotifierProvider(create: (_) => PowerSupplyTypeProvider()), diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 71d6e3a8..e8790342 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -36,6 +36,7 @@ import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import '../../models/new_models/department.dart'; import '../../new_views/swipe_module/dialoge/info_dialog.dart'; +import 'flow_medical_department_provider.dart'; import 'incident_attachment_model.dart'; class CreateIncidentRequestPage extends StatefulWidget { @@ -76,7 +77,7 @@ class _CreateIncidentRequestPageState extends State { void initState() { super.initState(); payload["id"] = 0; - payload["isFromMobile"] = 0; + payload["isFromMobile"] = true; payload["requestorUserId"] = context.userProvider.user!.userID; payload["requestorSiteId"] = context.userProvider.user!.clientId; } @@ -296,7 +297,7 @@ class _CreateIncidentRequestPageState extends State { }, ), 8.height, - SingleItemDropDownMenu( + SingleItemDropDownMenu( context: context, title: "Employee Department", showShadow: false, diff --git a/lib/modules/incident_module/flow_medical_department_provider.dart b/lib/modules/incident_module/flow_medical_department_provider.dart new file mode 100644 index 00000000..f2aa746b --- /dev/null +++ b/lib/modules/incident_module/flow_medical_department_provider.dart @@ -0,0 +1,32 @@ +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/modules/loan_module/models/medical_department_model.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +class FlowMedicalDepartmentProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading == true) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.getFlowMedicalDepartments); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => MedicalDepartmentModel.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart index ea9f9b1f..2b7f43ef 100644 --- a/lib/modules/incident_module/incident_detail_page.dart +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -41,7 +41,7 @@ class IncidentDetailPage extends StatelessWidget { appBar: const DefaultAppBar(title: "Request Details"), body: SafeArea( child: FutureBuilder( - future: Provider.of(context, listen: false).getIncidentById(97), + future: Provider.of(context, listen: false).getIncidentById(incidentId), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator(color: AppColor.primary10).center; if (snapshot.data == null) return const NoDataFound().center; diff --git a/lib/modules/incident_module/incident_item_view.dart b/lib/modules/incident_module/incident_item_view.dart index ff4db6fa..52838fb3 100644 --- a/lib/modules/incident_module/incident_item_view.dart +++ b/lib/modules/incident_module/incident_item_view.dart @@ -89,6 +89,7 @@ class IncidentItemView extends StatelessWidget { 8.height, infoWidget(label: context.translation.requestType, value: requestDetails!.requestType, context: context), infoWidget(label: "Incident No", value: requestDetails!.requestNo, context: context), + infoWidget(label: context.translation.assetName, value: requestDetails!.assetName, context: context), infoWidget(label: context.translation.site, value: requestDetails!.site, context: context), 8.height, Row( From 67f9081f507ba626f889041e3585231d2334d2e5 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 17 Feb 2026 12:44:38 +0300 Subject: [PATCH 07/11] ovr ticket information added. --- lib/controllers/api_routes/urls.dart | 1 + .../create_incident_request_page.dart | 1 + .../incident_module/incident_detail_page.dart | 120 +++++++++++------- .../incident_module/incident_provider.dart | 12 ++ 4 files changed, 85 insertions(+), 49 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 42facf90..a628f8e2 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -410,6 +410,7 @@ class URLs { static get getLoanById => '$_baseUrl/Loan/GetLoanById'; static get getIncidentById => '$_baseUrl/Incident/GetIncidentById'; + static get getOvrTicketDetails => '$_baseUrl/Incident/GetOvrTicketDetails'; static get addLoan => '$_baseUrl/Loan/AddLoan'; static get addIncident => '$_baseUrl/Incident/AddIncident'; diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index e8790342..995dc70f 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -76,6 +76,7 @@ class _CreateIncidentRequestPageState extends State { @override void initState() { super.initState(); + ovrSystem = Lookup(id: 81, name: "No", value: 0); payload["id"] = 0; payload["isFromMobile"] = true; payload["requestorUserId"] = context.userProvider.user!.userID; diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart index 2b7f43ef..2484a9e9 100644 --- a/lib/modules/incident_module/incident_detail_page.dart +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -39,59 +39,58 @@ class IncidentDetailPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: const DefaultAppBar(title: "Request Details"), - body: SafeArea( - child: FutureBuilder( - future: Provider.of(context, listen: false).getIncidentById(incidentId), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator(color: AppColor.primary10).center; - if (snapshot.data == null) return const NoDataFound().center; - - List allAttachments = snapshot.data!.incidentAttachments!; - - return ListView( - padding: const EdgeInsets.all(16), - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - StatusLabel( - label: snapshot.data!.incidentStatusName!, - textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.incidentStatusName!), - backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.incidentStatusName!), - ), - 1.width.expanded, - Text( - snapshot.data!.createdDate?.toServiceRequestCardFormat ?? "-", - textAlign: TextAlign.end, - style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), - ), - ], - ), - 12.height, - ...requesterDetails(context, snapshot.data!), - const Divider().defaultStyle(context), - ...requestDetails(context, snapshot.data!), - const Divider().defaultStyle(context), - ...assetDetails(context, snapshot.data!), - const Divider().defaultStyle(context), - ...personInvolvedDetails(context, snapshot.data!), - if (allAttachments.isNotEmpty) ...[ - const Divider().defaultStyle(context), + body: FutureBuilder( + future: Provider.of(context, listen: false).getIncidentById(incidentId), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator(color: AppColor.primary10).center; + if (snapshot.data == null) return const NoDataFound().center; + + List allAttachments = snapshot.data!.incidentAttachments!; + + return ListView( + padding: const EdgeInsets.all(16), + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: snapshot.data!.incidentStatusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.incidentStatusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.incidentStatusName!), + ), + 1.width.expanded, Text( - "Attachments".addTranslation, - style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + snapshot.data!.createdDate?.toServiceRequestCardFormat ?? "-", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), ), - FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), ], + ), + 12.height, + ...requesterDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...requestDetails(context, snapshot.data!), + if (snapshot.data!.ovrTicketNumber?.isNotEmpty ?? false) ovrInformation(context, snapshot.data!.ovrTicketNumber!), + const Divider().defaultStyle(context), + ...assetDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...personInvolvedDetails(context, snapshot.data!), + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), ], - ).toShadowContainer(context, padding: 12), - ], - ); - }), - )); + ], + ).toShadowContainer(context, padding: 12), + ], + ); + })); } List requestDetails(BuildContext context, IncidentDataModel incidentData) { @@ -113,6 +112,29 @@ class IncidentDetailPage extends StatelessWidget { ]; } + Widget ovrInformation(BuildContext context, String ovrNumber) { + return FutureBuilder?>( + future: Provider.of(context, listen: false).getOvrTicketDetails(ovrNumber), + builder: (BuildContext context, AsyncSnapshot?> snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator(color: AppColor.primary10).center; + if (snapshot.data == null) return const NoDataFound().center; + + Map? data = snapshot.data; + if (data == null) return const SizedBox(); + List keysList = data.keys.toList(); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider().defaultStyle(context), + Text("OVR Ticket Information", style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), + 'OVR Ticket Number: $ovrNumber'.bodyText(context), + for (int i = 0; i < keysList.length; i++) '${keysList[i]}: ${data[keysList[i]] ?? "-"}'.bodyText(context), + ], + ); + }); + } + List personInvolvedDetails(BuildContext context, IncidentDataModel incidentData) { return [ Text("Person Involved Information", style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), diff --git a/lib/modules/incident_module/incident_provider.dart b/lib/modules/incident_module/incident_provider.dart index bb692016..93f29939 100644 --- a/lib/modules/incident_module/incident_provider.dart +++ b/lib/modules/incident_module/incident_provider.dart @@ -37,4 +37,16 @@ class IncidentProvider extends ChangeNotifier { } return loanData; } + + Future?> getOvrTicketDetails(String ticketNumber) async { + try { + Response response = await ApiManager.instance.post(URLs.getOvrTicketDetails, body: {"ticketNumber": ticketNumber}, showToast: false); + if (response.statusCode >= 200 && response.statusCode < 300) { + return json.decode(response.body)["data"]; + } + } catch (error) { + print(error); + } + return null; + } } From 5d945e07cbdc8a31bf819d50138086ccbb4f43a6 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 19 Feb 2026 10:49:52 +0300 Subject: [PATCH 08/11] validation added, if asset not selected --- .../incident_module/create_incident_request_page.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 995dc70f..3e466bfe 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -616,6 +616,11 @@ class _CreateIncidentRequestPageState extends State { Future _submit() async { FocusScope.of(context).unfocus(); if (_formKey.currentState!.validate()) { + if (incidentType?.value == 1 && device == null) { + "Please scan or pick asset".showToast; + return; + } + if (occurrenceDate == null) { "Please select occurrence Data".showToast; return; From 6d4bc4694a74197566cc74060d419544db106238 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 19 Feb 2026 11:53:04 +0300 Subject: [PATCH 09/11] ovr ticker validation added. --- .../create_incident_request_page.dart | 63 ++++++++++++++++++- .../incident_module/incident_provider.dart | 4 ++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 3e466bfe..5ceb5dca 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -73,6 +73,15 @@ class _CreateIncidentRequestPageState extends State { Map payload = {}; + Map? ovrDetails; + + bool get isOvrVerified => ovrDetails != null; + + bool ovrVerifying = false; + String ovrTicketNumber = ""; + + TextEditingController ovrTicketController = TextEditingController(); + @override void initState() { super.initState(); @@ -372,6 +381,7 @@ class _CreateIncidentRequestPageState extends State { 8.height, AppTextFormField( initialValue: "", + controller: ovrTicketController, labelText: "OVR Ticket Number", validator: (value) { if ((value ?? "").isEmpty) return "Mandatory"; @@ -381,10 +391,56 @@ class _CreateIncidentRequestPageState extends State { backgroundColor: AppColor.fieldBgColor(context), labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), showShadow: false, + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + onPressed: () { + if (ovrVerifying || isOvrVerified) return; + if (ovrTicketNumber.isEmpty) { + "Please enter OVR ticket number".showToast; + return; + } + ovrVerifying = true; + setState(() {}); + context.read().getOvrTicketDetails(ovrTicketNumber).then((details) { + ovrDetails = details; + if (ovrDetails != null) { + payload["ovrTicketNumber"] = ovrTicketNumber; + "OVR Ticket verified successfully".showToast; + } + }).whenComplete(() { + ovrVerifying = false; + setState(() {}); + }); + }, + child: ovrVerifying + ? SizedBox( + width: 16.toScreenHeight, + height: 16.toScreenHeight, + child: const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), + ) + : isOvrVerified + ? const Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.done), Text("Verified")], + ) + : const Text("Verify"), + ), + if (isOvrVerified) + const Icon(Icons.clear, color: AppColor.red30).paddingOnly(end: 8).onPress(() { + ovrDetails = null; + ovrTicketNumber = ""; + ovrTicketController.clear(); + payload.remove("ovrTicketNumber"); + setState(() {}); + }), + ], + ), textInputType: TextInputType.name, textInputAction: TextInputAction.next, onChange: (value) { - payload["ovrTicketNumber"] = value; + ovrTicketNumber = value; }, ), ], @@ -621,6 +677,11 @@ class _CreateIncidentRequestPageState extends State { return; } + if (ovrSystem?.value == 1 && isOvrVerified == false) { + "Please verify OVR Ticket Number".showToast; + return; + } + if (occurrenceDate == null) { "Please select occurrence Data".showToast; return; diff --git a/lib/modules/incident_module/incident_provider.dart b/lib/modules/incident_module/incident_provider.dart index 93f29939..653854e3 100644 --- a/lib/modules/incident_module/incident_provider.dart +++ b/lib/modules/incident_module/incident_provider.dart @@ -42,6 +42,10 @@ class IncidentProvider extends ChangeNotifier { try { Response response = await ApiManager.instance.post(URLs.getOvrTicketDetails, body: {"ticketNumber": ticketNumber}, showToast: false); if (response.statusCode >= 200 && response.statusCode < 300) { + if (json.decode(response.body)["statusCode"] == 404) { + Fluttertoast.showToast(msg: json.decode(response.body)["message"] ?? "OVR Ticket not found"); + return null; + } return json.decode(response.body)["data"]; } } catch (error) { From 9226fe3ac741d7d919ce1b78591399b7c7c4adba Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 19 Feb 2026 14:07:37 +0300 Subject: [PATCH 10/11] asset room number --- lib/views/widgets/bottom_sheets/asset_detail_bottom_sheet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/widgets/bottom_sheets/asset_detail_bottom_sheet.dart b/lib/views/widgets/bottom_sheets/asset_detail_bottom_sheet.dart index 474ae1a3..3f7206af 100644 --- a/lib/views/widgets/bottom_sheets/asset_detail_bottom_sheet.dart +++ b/lib/views/widgets/bottom_sheets/asset_detail_bottom_sheet.dart @@ -63,7 +63,7 @@ class AssetDetailBottomSheet extends StatelessWidget { "${context.translation.building}: ${asset?.building?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), "${context.translation.floor}: ${asset?.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), "${context.translation.md}: ${asset?.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.room}: ${asset?.room?.value ?? "-"}".bodyText(context), + "${context.translation.room}: ${asset?.room?.name ?? "-"}".bodyText(context), ], ).expanded, ], From 260dfbcf068903254bd1666ff81c957d24011f42 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 23 Feb 2026 15:49:45 +0300 Subject: [PATCH 11/11] demoRequest model updated in asset model --- lib/models/device/asset_by_id_model.dart | 25 +++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/models/device/asset_by_id_model.dart b/lib/models/device/asset_by_id_model.dart index f0ff49bc..c631b767 100644 --- a/lib/models/device/asset_by_id_model.dart +++ b/lib/models/device/asset_by_id_model.dart @@ -14,7 +14,7 @@ class AssetByIdModel { AssetReplace? isParent; AssetInfo? parentAsset; AssetReplace? assetType; - String? demoRequest; + DemoRequest? demoRequest; String? missionCritical; Site? site; AssetReplace? building; @@ -143,7 +143,7 @@ class AssetByIdModel { isParent = json['isParent'] != null ? AssetReplace.fromJson(json['isParent']) : null; parentAsset = json['parentAsset'] != null ? AssetInfo.fromJson(json['parentAsset']) : null; assetType = json['assetType'] != null ? AssetReplace.fromJson(json['assetType']) : null; - demoRequest = json['demoRequest']; + demoRequest = json['demoRequest'] != null ? DemoRequest.fromJson(json['demoRequest']) : null; missionCritical = json['missionCritical']; site = json['site'] != null ? Site.fromJson(json['site']) : null; building = json['building'] != null ? AssetReplace.fromJson(json['building']) : null; @@ -229,7 +229,7 @@ class AssetByIdModel { if (assetType != null) { data['assetType'] = assetType?.toJson(); } - data['demoRequest'] = demoRequest; + data['demoRequest'] = demoRequest?.toJson(); data['missionCritical'] = missionCritical; if (site != null) { data['site'] = site?.toJson(); @@ -706,3 +706,22 @@ class AssetInfo { return map; } } + +class DemoRequest { + int? id; + String? label; + + DemoRequest({this.id, this.label}); + + DemoRequest.fromJson(Map json) { + id = json['id']; + label = json['label']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['label'] = label; + return data; + } +}