From 78d4d786a7d28e9aaa4f167f3bd5a4f18ee9b837 Mon Sep 17 00:00:00 2001 From: SurendarSuri30 Date: Tue, 9 Jun 2026 09:11:30 +0530 Subject: [PATCH] cliams bug fix --- .flutter-plugins-dependencies | 2 +- lib/core/config/env.dart | 4 +- lib/core/services/api_service.dart | 132 +- .../screens/Enquiry/enquiry/policy_popup.dart | 54 +- .../add_claim_stepper_dialog.dart | 2328 +++++++++++++++ .../Enquiry/policy_claims_endros/claims.dart | 2632 ++++++++++++----- .../create_endorsement_dialog.dart | 156 +- .../endorsomentUpdateValidation.dart | 197 +- .../widgets/policy_search_results_list.dart | 170 ++ web/index.html | 4 +- 10 files changed, 4676 insertions(+), 1003 deletions(-) create mode 100644 lib/presentation/screens/Enquiry/policy_claims_endros/add_claim_stepper_dialog.dart create mode 100644 lib/presentation/widgets/policy_search_results_list.dart diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index b36fc08..2312800 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.2.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_darwin-1.6.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_ios-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_flutter_pdfviewer","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-31.1.19/","native_build":true,"dependencies":["device_info_plus"],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.31/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_android-7.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_android-1.0.52/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"open_file_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_android-1.0.6/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.13/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_flutter_pdfviewer","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-31.1.19/","native_build":true,"dependencies":["device_info_plus"],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.2.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_darwin-1.6.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_mac","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_mac-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_pdfviewer_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_macos-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_linux-0.0.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"syncfusion_pdfviewer_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_linux-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_windows-1.0.11/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_windows-0.0.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"syncfusion_pdfviewer_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_windows-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","dependencies":[],"dev_dependency":false},{"name":"firebase_auth_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth_web-6.1.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core_web-3.3.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_messaging_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging_web-4.1.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"flutter_secure_storage_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","dependencies":[],"dev_dependency":false},{"name":"google_sign_in_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_web-1.1.0/","dependencies":[],"dev_dependency":false},{"name":"open_file_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_web-0.0.4/","dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"syncfusion_pdfviewer_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_web-31.1.19/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"firebase_auth","dependencies":["firebase_auth_web","firebase_core"]},{"name":"firebase_auth_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_messaging","dependencies":["firebase_core","firebase_messaging_web"]},{"name":"firebase_messaging_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"fluttertoast","dependencies":[]},{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"local_auth","dependencies":["local_auth_android","local_auth_darwin","local_auth_windows"]},{"name":"local_auth_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"local_auth_darwin","dependencies":[]},{"name":"local_auth_windows","dependencies":[]},{"name":"open_file","dependencies":["open_file_android","open_file_web","open_file_ios","open_file_mac","open_file_windows","open_file_linux"]},{"name":"open_file_android","dependencies":[]},{"name":"open_file_ios","dependencies":[]},{"name":"open_file_linux","dependencies":[]},{"name":"open_file_mac","dependencies":[]},{"name":"open_file_web","dependencies":[]},{"name":"open_file_windows","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"pdf_render","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"syncfusion_flutter_pdfviewer","dependencies":["url_launcher","device_info_plus","syncfusion_pdfviewer_web","syncfusion_pdfviewer_macos","syncfusion_pdfviewer_windows","syncfusion_pdfviewer_linux"]},{"name":"syncfusion_pdfviewer_linux","dependencies":[]},{"name":"syncfusion_pdfviewer_macos","dependencies":[]},{"name":"syncfusion_pdfviewer_web","dependencies":[]},{"name":"syncfusion_pdfviewer_windows","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2026-04-18 19:10:00.041784","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.2.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_darwin-1.6.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_ios-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_flutter_pdfviewer","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-31.1.19/","native_build":true,"dependencies":["device_info_plus"],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.31/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_android-7.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_android-1.0.52/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"open_file_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_android-1.0.6/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.13/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_flutter_pdfviewer","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-31.1.19/","native_build":true,"dependencies":["device_info_plus"],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.2.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_darwin-1.6.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_mac","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_mac-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_pdfviewer_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_macos-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_linux-0.0.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"syncfusion_pdfviewer_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_linux-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_windows-1.0.11/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_windows-0.0.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"syncfusion_pdfviewer_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_windows-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","dependencies":[],"dev_dependency":false},{"name":"firebase_auth_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth_web-6.1.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core_web-3.3.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_messaging_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging_web-4.1.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"flutter_secure_storage_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","dependencies":[],"dev_dependency":false},{"name":"google_sign_in_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_web-1.1.0/","dependencies":[],"dev_dependency":false},{"name":"open_file_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_web-0.0.4/","dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"syncfusion_pdfviewer_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_web-31.1.19/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"firebase_auth","dependencies":["firebase_auth_web","firebase_core"]},{"name":"firebase_auth_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_messaging","dependencies":["firebase_core","firebase_messaging_web"]},{"name":"firebase_messaging_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"fluttertoast","dependencies":[]},{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"local_auth","dependencies":["local_auth_android","local_auth_darwin","local_auth_windows"]},{"name":"local_auth_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"local_auth_darwin","dependencies":[]},{"name":"local_auth_windows","dependencies":[]},{"name":"open_file","dependencies":["open_file_android","open_file_web","open_file_ios","open_file_mac","open_file_windows","open_file_linux"]},{"name":"open_file_android","dependencies":[]},{"name":"open_file_ios","dependencies":[]},{"name":"open_file_linux","dependencies":[]},{"name":"open_file_mac","dependencies":[]},{"name":"open_file_web","dependencies":[]},{"name":"open_file_windows","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"pdf_render","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"syncfusion_flutter_pdfviewer","dependencies":["url_launcher","device_info_plus","syncfusion_pdfviewer_web","syncfusion_pdfviewer_macos","syncfusion_pdfviewer_windows","syncfusion_pdfviewer_linux"]},{"name":"syncfusion_pdfviewer_linux","dependencies":[]},{"name":"syncfusion_pdfviewer_macos","dependencies":[]},{"name":"syncfusion_pdfviewer_web","dependencies":[]},{"name":"syncfusion_pdfviewer_windows","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2026-06-03 09:17:41.506394","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/lib/core/config/env.dart b/lib/core/config/env.dart index 5c4d374..4ff1f1c 100644 --- a/lib/core/config/env.dart +++ b/lib/core/config/env.dart @@ -5,8 +5,8 @@ class Env { ); static const String apiUrl = String.fromEnvironment( 'API_URL', - defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */ - // defaultValue: 'https://venbait.in/nhance/partner/dev/api/', /* Test build (enable index.html line 19) */ + // defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */ + defaultValue: 'https://venbait.in/nhance/partner/dev/api/', /* Test build (enable index.html line 19) */ // defaultValue: 'http://localhost/nhance_partner_be/', /* localhost build (enable index.html line 19) */ ); // static const String baseUrl = String.fromEnvironment( diff --git a/lib/core/services/api_service.dart b/lib/core/services/api_service.dart index 4a563c5..a07f2d0 100644 --- a/lib/core/services/api_service.dart +++ b/lib/core/services/api_service.dart @@ -1895,37 +1895,77 @@ class ApiService { return response; } - Future> fetchClaimList( - managerId, - int userId, - role, - ) async { - // print(_token); + /// GET /claims with optional filters (e.g. manager_id, claim_type_id, from_date). + Future> fetchClaims({ + int? managerId, + int? agentId, + int? handlerId, + int? staffId, + int? claimTypeId, + int? claimStatusId, + int? insurerId, + String? policyNumber, + String? fromDate, + String? toDate, + }) async { if (_token == null) { await _initializeToken(); } - final url; - - if (role == 'manager') { - url = Uri.parse('${Env.apiUrl}claim/ClaimList?manager_id=$managerId'); - } else if (role == 'Accounts') { - url = Uri.parse('${Env.apiUrl}claim/ClaimList?manager_id=$managerId'); - } else if (role == 'agent') { - url = Uri.parse('${Env.apiUrl}claim/ClaimList?agent_id=$userId'); - } else if (role == 'handler') { - url = Uri.parse('${Env.apiUrl}claim/ClaimList?handler_id=$userId'); - } else { - url = Uri.parse('${Env.apiUrl}claim/ClaimList?staff_id=$userId'); + final query = {}; + void addInt(String key, int? value) { + if (value != null) query[key] = value.toString(); } + void addStr(String key, String? value) { + final trimmed = value?.trim(); + if (trimmed != null && trimmed.isNotEmpty) { + query[key] = trimmed; + } + } + + addInt('manager_id', managerId); + addInt('agent_id', agentId); + addInt('handler_id', handlerId); + addInt('staff_id', staffId); + addInt('claim_type_id', claimTypeId); + addInt('claim_status_id', claimStatusId); + addInt('insurer_id', insurerId); + addStr('policy_number', policyNumber); + addStr('from_date', fromDate); + addStr('to_date', toDate); + + final url = Uri.parse('${Env.apiUrl}claim/ClaimList').replace( + queryParameters: query.isEmpty ? null : query, + ); final headers = { 'Authorization': 'Bearer $_token' ?? '', 'app-signature': Env.App_Signature, }; - final response = await _makeGetRequest(url, headers); - return response; + return _makeGetRequest(url, headers); + } + + @Deprecated('Use fetchClaims with query parameters') + Future> fetchClaimList( + managerId, + int userId, + role, + ) async { + final roleStr = role?.toString().toLowerCase() ?? ''; + return fetchClaims( + managerId: (roleStr == 'manager' || roleStr == 'accounts') + ? int.tryParse(managerId?.toString() ?? '') + : null, + agentId: roleStr == 'agent' ? userId : null, + handlerId: roleStr == 'handler' ? userId : null, + staffId: (roleStr != 'manager' && + roleStr != 'accounts' && + roleStr != 'agent' && + roleStr != 'handler') + ? userId + : null, + ); } Future> findSingleClaimData(id) async { @@ -1943,6 +1983,54 @@ class ApiService { return response; } + /// GET claim/claimStatusList — active claim statuses for filters & forms. + Future> fetchClaimStatusList() async { + if (_token == null) { + await _initializeToken(); + } + final url = Uri.parse('${Env.apiUrl}claim/claimStatusList'); + final headers = { + 'Authorization': 'Bearer $_token' ?? '', + 'app-signature': Env.App_Signature, + }; + return _makeGetRequest(url, headers); + } + + Future> fetchClaimById(dynamic id) async { + if (_token == null) { + await _initializeToken(); + } + final url = Uri.parse( + '${Env.apiUrl}claim/getClaimById', + ).replace(queryParameters: {'id': id.toString()}); + final headers = { + 'Authorization': 'Bearer $_token', + 'app-signature': Env.App_Signature, + }; + return _makeGetRequest(url, headers); + } + + /// GET claim/deleteClaim?id=&updated_by= + Future> deleteClaim({ + required dynamic id, + required dynamic updatedBy, + }) async { + if (_token == null) { + await _initializeToken(); + } + final url = Uri.parse('${Env.apiUrl}claim/deleteClaim').replace( + queryParameters: { + 'id': id.toString(), + 'updated_by': updatedBy.toString(), + }, + ); + final headers = { + 'Authorization': 'Bearer $_token' ?? '', + 'app-signature': Env.App_Signature, + }; + return _makeGetRequest(url, headers); + } + // ----------------------------------- ENDORSEMENT ------------------------------------------------- Future> fetchEndorsementList( @@ -2047,6 +2135,10 @@ class ApiService { url = Uri.parse('${Env.apiUrl}master/getStaffMaster'); } else if (val == 'Claim') { url = Uri.parse('${Env.apiUrl}master/getClaimMaster'); + } else if (val == 'Claim Status' || val == 'ClaimStatus') { + url = Uri.parse('${Env.apiUrl}master/getClaimStatusMaster'); + } else if (val == 'Status') { + url = Uri.parse('${Env.apiUrl}master/getStatusMaster'); } else if (val == 'Endorsement') { url = Uri.parse('${Env.apiUrl}master/getEndorsementMaster'); } else if (val == 'Broker') { diff --git a/lib/presentation/screens/Enquiry/enquiry/policy_popup.dart b/lib/presentation/screens/Enquiry/enquiry/policy_popup.dart index 1e7daa2..f66f76a 100644 --- a/lib/presentation/screens/Enquiry/enquiry/policy_popup.dart +++ b/lib/presentation/screens/Enquiry/enquiry/policy_popup.dart @@ -18,6 +18,7 @@ import '../../../themes/indicators/customizd_file_upload.dart'; import '../../../themes/indicators/input_field_decoration.dart'; import '../../../themes/indicators/search_field_theme.dart'; import '../../../themes/indicators/text_field_theme.dart'; +import '../../../widgets/policy_search_results_list.dart'; // 🔹 Custom Dialog Widget class AddDialog extends StatefulWidget { @@ -629,43 +630,24 @@ class _AddDialogState extends State { ? null : MediaQuery.of(context).size.width * 0.26, ), - // Vehicle Search Results if (filteredVehicleData.isNotEmpty) - Container( - width: - MediaQuery.of(context).size.width * 0.26, // optional - height: 150, - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.blueGrey.shade100), - borderRadius: BorderRadius.circular(5), - ), - child: ListView.builder( - itemCount: filteredVehicleData.length, - itemBuilder: (context, index) { - final vehicle = filteredVehicleData[index]; - return InkWell( - onTap: () { - setState(() { - controllers['policyNum']?.text = - vehicle['policy_number']; - filteredVehicleData.clear(); - }); - }, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 3, - horizontal: 8, - ), - // color: Colors.amber, - child: Text( - vehicle['policy_number'] ?? '-', - style: _textStyle1, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ); + SizedBox( + width: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.26, + child: PolicySearchResultsList( + rows: filteredVehicleData, + fromVehicle: true, + onSelected: (vehicle) { + setState(() { + controllers['policyNum']?.text = + PolicySearchUtils.policyNumberFromRow(vehicle); + _vehicleSearchController.text = + PolicySearchUtils.vehicleInputValueOnSelect( + vehicle, + ); + filteredVehicleData.clear(); + }); }, ), ), diff --git a/lib/presentation/screens/Enquiry/policy_claims_endros/add_claim_stepper_dialog.dart b/lib/presentation/screens/Enquiry/policy_claims_endros/add_claim_stepper_dialog.dart new file mode 100644 index 0000000..7b66ef4 --- /dev/null +++ b/lib/presentation/screens/Enquiry/policy_claims_endros/add_claim_stepper_dialog.dart @@ -0,0 +1,2328 @@ +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; + +import '../../../../core/config/env.dart'; +import '../../../../core/services/api_service.dart'; +import '../../../../data/services/auth_service.dart'; +import '../../../../data/utils/toastNotification.dart'; +import '../../../../data/utils/validators.dart'; +import '../../../layouts/responsive_layout.dart'; +import '../../../widgets/policy_search_results_list.dart'; + +/// Four-step Add/Edit Claim flow; submits via [claim/createClaim] or [claim/updateClaim]. +class AddClaimStepperDialog extends StatefulWidget { + const AddClaimStepperDialog({ + super.key, + required this.userId, + required this.managerId, + required this.role, + required this.onSuccess, + this.claimToEdit, + }); + + final dynamic userId; + final dynamic managerId; + final dynamic role; + final VoidCallback onSuccess; + /// When set, dialog runs in edit mode and calls [claim/updateClaim]. + final Map? claimToEdit; + + @override + State createState() => _AddClaimStepperDialogState(); +} + +class _AddClaimStepperDialogState extends State { + static const Color _teal = Color(0xFF14A492); + static const Color _border = Color(0xFFE2E8F0); + static const Color _muted = Color(0xFF64748B); + static const double _fieldHeight = 40; + + late ApiService _api; + String? _token; + bool _loading = false; + bool _searchLoading = false; + int _step = 0; + + final _vehicleSearch = TextEditingController(); + final _externalVehicleNo = TextEditingController(); + final _policySearch = TextEditingController(); + final _policyNum = TextEditingController(); + final _claimsNumber = TextEditingController(); + final _insuredName = TextEditingController(); + + final _accidentDt = TextEditingController(); + final _accidentPlace = TextEditingController(); + final _claimDesc = TextEditingController(); + final _spotSurveyorName = TextEditingController(); + final _workshopSurveyorName = TextEditingController(); + final _spotSurveyorContact = TextEditingController(); + final _workshopSurveyorContact = TextEditingController(); + final _remark = TextEditingController(); + final _partner = TextEditingController(); + final _broker = TextEditingController(); + final _insurer = TextEditingController(); + + String? _policyFrom; + String? _selectedClaimTypeId; + String? _regNo; + String? _claimId; + String? _selectedClaimStatusId; + /// True when claim was loaded from API already SETTLED (not user dropdown change). + bool _apiClaimStatusWasSettled = false; + Map? _lastApiClaimItem; + String? _mobile; + String? _mail; + String? _insurerId; + String? _brokerId; + String? _partnerId; + + bool get _isEdit => widget.claimToEdit != null; + + static const int _maxFileSlots = 6; + final List _pickedFiles = []; + final List> _existingFiles = []; + final List _removedExistingFileIds = []; + + List> _claimTypes = []; + List> _claimStatuses = []; + List> _insurerList = []; + List> _brokerList = []; + List> _partnerList = []; + + List> _vehicleHits = []; + List> _policyHits = []; + + final _policyFromOptions = const ['Internal', 'External']; + + bool get _isInternal => _policyFrom == 'Internal'; + bool get _isExternal => _policyFrom == 'External'; + + @override + void initState() { + super.initState(); + _api = ApiService(); + _policyFrom = _policyFromOptions.first; + if (_isEdit) { + _populateFromClaim(widget.claimToEdit!); + } + _initTokenAndMasters(); + } + + Future _initTokenAndMasters() async { + await _initializeToken(); + if (_isEdit && mounted) setState(() => _loading = true); + final futures = [ + _loadClaimTypes(), + _loadClaimStatuses(), + ]; + if (_isEdit) futures.add(_fetchAndApplyClaim()); + if (_isEdit && _isExternal) futures.add(_loadExternalMasters()); + await Future.wait(futures); + if (!mounted) return; + if (_isEdit && _lastApiClaimItem != null) { + _apiClaimStatusWasSettled = + _claimItemStatusIsSettled(_lastApiClaimItem!); + } + setState(() { + if (_isEdit) _loading = false; + if (!_isEdit && + (_selectedClaimStatusId == null || _selectedClaimStatusId!.isEmpty) && + _claimStatuses.isNotEmpty) { + _selectedClaimStatusId = _claimStatuses.first['id']?.toString(); + } + }); + } + + Future _fetchAndApplyClaim() async { + final id = _claimId ?? widget.claimToEdit?['id']?.toString(); + if (id == null || id.isEmpty) return; + try { + final r = await _api.fetchClaimById(id); + if (!mounted) return; + final ok = r['status'] == 'success' || + r['status'] == 200 || + r['status'] == '200'; + if (!ok) return; + final data = r['data']; + if (data is Map) { + _existingFiles.clear(); + _insurerId = null; + _brokerId = null; + _partnerId = null; + _insurer.clear(); + _broker.clear(); + _partner.clear(); + _populateFromClaim(Map.from(data)); + } + } catch (e) { + debugPrint('fetchClaimById error: $e'); + } + } + + String _pickClaimField(Map row, List keys) { + for (final k in keys) { + final v = row[k]; + if (v != null && v.toString().trim().isNotEmpty) { + return v.toString().trim(); + } + } + return ''; + } + + void _populateFromClaim(Map item) { + _lastApiClaimItem = Map.from(item); + _claimId = item['id']?.toString(); + _policyNum.text = _pickClaimField(item, ['policy_number', 'policy_no']); + _claimsNumber.text = _pickClaimField(item, ['claim_number', 'claims_number']); + final policyFrom = _pickClaimField(item, ['policy_from']); + if (policyFrom.isNotEmpty && + _policyFromOptions.contains(policyFrom)) { + _policyFrom = policyFrom; + } + _insuredName.text = _pickClaimField(item, [ + 'insured_name', + 'customer_name', + 'proposer_name', + ]); + _regNo = PolicySearchUtils.rcNoFromRow(item); + if (_isInternal && _regNo != null && _regNo!.isNotEmpty) { + _vehicleSearch.text = _regNo!; + } + if (_isExternal) { + final reg = _pickClaimField(item, ['reg_no']); + if (reg.isNotEmpty) _externalVehicleNo.text = reg; + } + _selectedClaimTypeId = _pickClaimField(item, ['claim_type_id', 'claim_type']); + if ((_selectedClaimTypeId ?? '').isEmpty) _selectedClaimTypeId = null; + _claimDesc.text = _pickClaimField(item, ['claim_description']); + _remark.text = _pickClaimField(item, ['remark', 'remark']); + _accidentPlace.text = _pickClaimField(item, [ + 'place_of_incident', + 'accident_place', + ]); + final incidentRaw = _pickClaimField(item, [ + 'date_of_incident', + 'accident_date', + 'accident_datetime', + ]); + if (incidentRaw.isNotEmpty) { + _accidentDt.text = _formatDateOfIncidentForDisplay(incidentRaw); + } + _spotSurveyorName.text = + _pickClaimField(item, ['spot_surveyor_name']); + _spotSurveyorContact.text = _contactDigitsOnly( + _pickClaimField(item, ['spot_surveyor_contact']), + ); + _workshopSurveyorName.text = + _pickClaimField(item, ['workshop_surveyor_name']); + _workshopSurveyorContact.text = _contactDigitsOnly( + _pickClaimField(item, ['workshop_surveyor_contact']), + ); + _selectedClaimStatusId = _pickClaimField(item, ['claim_status_id']); + if ((_selectedClaimStatusId ?? '').isEmpty) _selectedClaimStatusId = null; + _apiClaimStatusWasSettled = _claimItemStatusIsSettled(item); + _mobile = _pickClaimField(item, ['mobile', 'customer_mobile']); + _mail = _pickClaimField(item, ['mail', 'email']); + + _partnerId = _pickClaimField(item, ['agent_id', 'partner_id']); + if (_partnerId!.isEmpty) _partnerId = null; + final agentName = _pickClaimField(item, [ + 'agent_name', + 'partner_name', + 'partner', + ]); + _partner.text = agentName.isNotEmpty ? agentName : (_partnerId ?? ''); + + _brokerId = _pickClaimField(item, ['broker_id']); + if (_brokerId!.isEmpty) _brokerId = null; + final brokerName = _pickClaimField(item, ['broker_name']); + _broker.text = brokerName; + + _insurerId = _pickClaimField(item, ['insurer_id']); + if (_insurerId!.isEmpty) _insurerId = null; + final insurerName = _pickClaimField(item, [ + 'insurer_short_name', + 'insurer_name', + ]); + _insurer.text = insurerName; + + final rawFiles = item['files']; + if (rawFiles is List) { + for (final f in rawFiles) { + if (f is Map) { + _existingFiles.add(Map.from(f)); + } + } + } + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + } + + Future _loadClaimTypes() async { + try { + final r = await _api.fetchMasterDropDown('Claim'); + if ((r['status'] == 'success' || r['status'] == 200) && + r['data'] is List) { + setState(() { + _claimTypes = List>.from(r['data']); + }); + } + } catch (_) {} + } + + Future _loadExternalMasters() async { + try { + final results = await Future.wait([ + _api.fetchMasterDropDown('Insurers'), + _api.fetchMasterDropDown('Broker'), + if (widget.managerId != null) + _api.fetchAgentNameDropDown(widget.managerId) + else + Future.value({'status': 'success', 'data': []}), + ]); + if (!mounted) return; + setState(() { + if (_isSuccessResponse(results[0]) && results[0]['data'] is List) { + _insurerList = + List>.from(results[0]['data'] as List); + } + if (_isSuccessResponse(results[1]) && results[1]['data'] is List) { + _brokerList = + List>.from(results[1]['data'] as List); + } + if (_isSuccessResponse(results[2]) && results[2]['data'] is List) { + _partnerList = + List>.from(results[2]['data'] as List); + } + }); + } catch (e) { + debugPrint('Load external masters error: $e'); + } + } + + Future _loadClaimStatuses() async { + try { + final r = await _api.fetchClaimStatusList(); + if ((r['status'] == 'success' || r['status'] == 200) && + r['data'] is List) { + if (mounted) { + setState(() { + _claimStatuses = List>.from(r['data']); + }); + } + } + } catch (_) {} + } + + bool _isSuccessResponse(dynamic r) { + if (r is! Map) return false; + final s = r['status']; + return s == 'success' || s == 200 || s == '200'; + } + + void _applyPolicySelection(Map row, {required bool fromVehicle}) { + final policyNo = PolicySearchUtils.policyNumberFromRow(row); + final rcNo = PolicySearchUtils.rcNoFromRow(row); + final name = PolicySearchUtils.insuredNameFromRow(row); + final brokerName = (row['broker_name'] ?? '').toString().trim(); + final brokerId = (row['broker_id'] ?? '').toString().trim(); + final agentId = (row['agent_id'] ?? row['partner_id'] ?? '').toString().trim(); + final agentName = (row['agent_name'] ?? + row['partner_name'] ?? + row['partner'] ?? + '') + .toString() + .trim(); + final insurerId = (row['insurer_id'] ?? '').toString().trim(); + final insurerName = (row['insurer_short_name'] ?? + row['insurer_name'] ?? + row['insurer'] ?? + '') + .toString() + .trim(); + + setState(() { + if (policyNo.isNotEmpty) _policyNum.text = policyNo; + if (rcNo != null && rcNo.isNotEmpty) _regNo = rcNo; + if (fromVehicle) { + _vehicleSearch.text = PolicySearchUtils.vehicleInputValueOnSelect(row); + } else if (policyNo.isNotEmpty) { + _policySearch.text = policyNo; + } + if (name.isNotEmpty) _insuredName.text = name; + final mob = (row['mobile'] ?? row['customer_mobile'] ?? '').toString().trim(); + final mail = (row['mail'] ?? row['email'] ?? '').toString().trim(); + if (mob.isNotEmpty) _mobile = mob; + if (mail.isNotEmpty) _mail = mail; + _partnerId = agentId.isEmpty ? null : agentId; + _partner.text = agentName.isNotEmpty ? agentName : agentId; + _brokerId = brokerId.isEmpty ? null : brokerId; + _broker.text = brokerName; + _insurerId = insurerId.isEmpty ? null : insurerId; + _insurer.text = insurerName; + _vehicleHits = []; + _policyHits = []; + }); + } + + Future _runVehicleSearch([String? query]) async { + final q = (query ?? _vehicleSearch.text).trim(); + if (q.isEmpty) { + setState(() => _vehicleHits = []); + return; + } + setState(() => _searchLoading = true); + try { + final r = await _api.fetchVehicleSearch(q); + if (!mounted) return; + if (_isSuccessResponse(r)) { + setState(() { + _vehicleHits = PolicySearchUtils.normalizeData(r['data']); + _policyHits = []; + }); + } else { + setState(() => _vehicleHits = []); + } + } catch (e) { + debugPrint('Vehicle search error: $e'); + if (mounted) setState(() => _vehicleHits = []); + } finally { + if (mounted) setState(() => _searchLoading = false); + } + } + + Future _runPolicySearch([String? query]) async { + final q = (query ?? _policySearch.text).trim(); + if (q.isEmpty) { + setState(() => _policyHits = []); + return; + } + setState(() => _searchLoading = true); + try { + final r = await _api.fetchPolicySearch(q); + if (!mounted) return; + if (_isSuccessResponse(r)) { + setState(() { + _policyHits = PolicySearchUtils.normalizeData(r['data']); + _vehicleHits = []; + }); + } else { + setState(() => _policyHits = []); + } + } catch (e) { + debugPrint('Policy search error: $e'); + if (mounted) setState(() => _policyHits = []); + } finally { + if (mounted) setState(() => _searchLoading = false); + } + } + + String _claimTypeLabel(Map m) { + return (m['claim_type'] ?? m['name'] ?? '').toString(); + } + + Future _pickFile() async { + final r = await FilePicker.platform.pickFiles(withData: true); + if (r != null && r.files.isNotEmpty) { + setState(() { + _pickedFiles.add(r.files.first); + }); + } + } + + void _removeExistingFile(int index) { + if (index < 0 || index >= _existingFiles.length) return; + final removed = _existingFiles.removeAt(index); + final id = removed['id']?.toString(); + if (id != null && id.isNotEmpty) _removedExistingFileIds.add(id); + setState(() {}); + } + + void _removePickedFile(int index) { + if (index < 0 || index >= _pickedFiles.length) return; + setState(() => _pickedFiles.removeAt(index)); + } + + String? _existingFileId(Map file) { + final id = file['id'] ?? file['file_id']; + if (id == null) return null; + final s = id.toString().trim(); + return s.isEmpty ? null : s; + } + + String _existingFileDownloadPath(Map file) { + for (final key in ['download_url', 'preview_url', 'file_path', 'file_url']) { + final raw = file[key]; + if (raw == null) continue; + final s = raw.toString().trim(); + if (s.isEmpty) continue; + final idx = s.indexOf('claim/downloadClaimFile'); + if (idx >= 0) { + final path = s.substring(idx); + return path.startsWith('/') ? path.substring(1) : path; + } + if (s.startsWith('http') && s.contains('/api/')) { + final afterApi = s.split('/api/').last; + if (afterApi.isNotEmpty) return afterApi; + } + } + final id = _existingFileId(file); + return id != null ? 'claim/downloadClaimFile?file_id=$id' : ''; + } + + Future _downloadExistingFile(Map file) async { + final path = _existingFileDownloadPath(file); + final id = _existingFileId(file); + if (path.isEmpty || id == null) { + if (mounted) { + ToastHelper.showErrorToast(context, 'Download not available for this file'); + } + return; + } + await _api.downloadFile( + apiUrl: path, + apiId: id, + localFile: null, + fileName: _existingFileName(file), + ); + } + + Future _downloadPickedFile(PlatformFile file) async { + if (file.bytes == null) { + if (mounted) { + ToastHelper.showErrorToast(context, 'File data not available'); + } + return; + } + await _api.downloadFile( + apiUrl: null, + localFile: file, + fileName: file.name, + ); + } + + Future _pickAccidentDate() async { + final initial = _parseAccidentDate() ?? DateTime.now(); + final firstDate = DateTime(2000); + final lastDate = DateTime.now().add(const Duration(days: 1)); + final pickedDate = await showDatePicker( + context: context, + initialDate: initial.isBefore(firstDate) || initial.isAfter(lastDate) + ? DateTime.now() + : initial, + firstDate: firstDate, + lastDate: lastDate, + builder: (context, child) => Theme( + data: Theme.of(context).copyWith( + colorScheme: Theme.of(context).colorScheme.copyWith(primary: _teal), + ), + child: child!, + ), + ); + if (pickedDate == null || !mounted) return; + setState(() { + _accidentDt.text = DateFormat('dd-MM-yyyy').format(pickedDate); + }); + } + + DateTime? _parseAccidentDate() { + final t = _accidentDt.text.trim(); + if (t.isEmpty) return null; + final patterns = [ + DateFormat('dd-MM-yyyy'), + DateFormat('dd-MM-yyyy hh:mm a'), + DateFormat('dd-MM-yyyy HH:mm'), + DateFormat('yyyy-MM-dd HH:mm:ss'), + DateFormat('yyyy-MM-dd'), + ]; + for (final p in patterns) { + try { + return p.parse(t); + } catch (_) {} + } + return null; + } + + String? _claimStatusLabel(Map m) { + return (m['claim_status'] ?? + m['status'] ?? + m['name'] ?? + m['claim_status_value'] ?? + m['status_name'] ?? + '') + .toString() + .trim(); + } + + String? _statusLabelForId(String? id) { + if (id == null || id.isEmpty) return null; + for (final m in _claimStatuses) { + if (m['id']?.toString() == id) return _claimStatusLabel(m); + } + return null; + } + + bool _claimItemStatusIsSettled(Map item) { + final label = _pickClaimField(item, [ + 'claim_status_value', + 'claim_status', + 'status', + ]).toUpperCase(); + if (label.contains('SETTLED')) return true; + final statusId = _pickClaimField(item, ['claim_status_id']); + if (statusId.isEmpty) return false; + for (final m in _claimStatuses) { + if (m['id']?.toString() != statusId) continue; + final statusLabel = (_claimStatusLabel(m) ?? '').toUpperCase(); + if (statusLabel.contains('SETTLED')) return true; + } + return false; + } + + /// Disable save only when API-loaded status was already SETTLED. + bool get _isSaveClaimDisabled => _isEdit && _apiClaimStatusWasSettled; + + /// API format: `dd-MM-yyyy`. + String? _formatDateOfIncidentForApi(String raw) { + final t = raw.trim(); + if (t.isEmpty) return null; + final patterns = [ + DateFormat('dd-MM-yyyy'), + DateFormat('d-M-yyyy'), + DateFormat('dd-MM-yyyy hh:mm a'), + DateFormat('dd-MM-yyyy HH:mm'), + DateFormat('yyyy-MM-dd HH:mm:ss'), + DateFormat('yyyy-MM-dd'), + ]; + for (final p in patterns) { + try { + final dt = p.parse(t); + return DateFormat('dd-MM-yyyy').format(dt); + } catch (_) {} + } + return t; + } + + String _formatDateOfIncidentForDisplay(String raw) { + final api = _formatDateOfIncidentForApi(raw); + return api ?? raw; + } + + void _putField(Map fields, String key, String? value) { + final v = value?.trim(); + if (v != null && v.isNotEmpty) fields[key] = v; + } + + String? _agentIdFromPartner() { + final id = _partnerId?.trim(); + if (id != null && id.isNotEmpty) return id; + final p = _partner.text.trim(); + if (p.isEmpty) return null; + return int.tryParse(p) != null ? p : null; + } + + String? _vehicleNoForApi() { + if (_isExternal) { + final v = _externalVehicleNo.text.trim(); + return v.isEmpty ? null : v; + } + final fromReg = _regNo?.trim(); + if (fromReg != null && fromReg.isNotEmpty) return fromReg; + final fromSearch = _vehicleSearch.text.trim(); + return fromSearch.isEmpty ? null : fromSearch; + } + + void _resetOriginFields() { + _vehicleSearch.clear(); + _externalVehicleNo.clear(); + _policySearch.clear(); + _policyNum.clear(); + _claimsNumber.clear(); + _insuredName.clear(); + _insurer.clear(); + _broker.clear(); + _partner.clear(); + _vehicleHits = []; + _policyHits = []; + _regNo = null; + _insurerId = null; + _brokerId = null; + _partnerId = null; + _mobile = null; + _mail = null; + } + + Map? _findMasterItem( + List> list, + String? id, + ) { + if (id == null || id.isEmpty) return null; + for (final item in list) { + if (item['id']?.toString() == id) return item; + } + return null; + } + + String _masterItemLabel(Map item, {bool partner = false}) { + if (partner) { + final code = item['agent_code']?.toString() ?? ''; + final name = item['agent_name']?.toString() ?? + item['name']?.toString() ?? + ''; + return code.isNotEmpty ? '$code - $name' : name; + } + return item['short_name']?.toString() ?? + item['name']?.toString() ?? + item['value']?.toString() ?? + ''; + } + + void _onPolicyFromChanged(String? value) { + if (value == null || value == _policyFrom) return; + if (value == 'External') { + _resetOriginFields(); + _loadExternalMasters(); + setState(() => _policyFrom = value); + } else { + setState(() { + _policyFrom = value; + _resetOriginFields(); + }); + } + } + + Widget _buildInsurerOriginField() { + if (_isInternal) { + return _labeledField( + 'Insurer', + TextFormField( + controller: _insurer, + readOnly: true, + style: _fieldValueStyle, + decoration: _fieldDeco().copyWith( + hintText: 'Auto-fetched on search', + ), + ), + ); + } + return _labeledField( + 'Insurer', + _uniformHeightField( + DropdownSearch>( + selectedItem: _findMasterItem(_insurerList, _insurerId), + items: (f, i) => _insurerList, + itemAsString: (v) => _masterItemLabel(v), + compareFn: (a, b) => + (a['id']?.toString() ?? '') == (b['id']?.toString() ?? ''), + onChanged: (v) { + setState(() { + _insurerId = v?['id']?.toString(); + _insurer.text = v != null ? _masterItemLabel(v) : ''; + }); + }, + decoratorProps: DropDownDecoratorProps( + decoration: _fieldDeco().copyWith(hintText: 'Select insurer'), + baseStyle: _fieldValueStyle, + ), + popupProps: const PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + ), + ), + ), + ); + } + + Widget _buildBrokerOriginField() { + if (_isInternal) { + return _labeledField( + 'Broker', + TextFormField( + controller: _broker, + readOnly: true, + style: _fieldValueStyle, + decoration: _fieldDeco().copyWith( + hintText: 'Auto-fetched on search', + ), + ), + ); + } + return _labeledField( + 'Broker', + _uniformHeightField( + DropdownSearch>( + selectedItem: _findMasterItem(_brokerList, _brokerId), + items: (f, i) => _brokerList, + itemAsString: (v) => _masterItemLabel(v), + compareFn: (a, b) => + (a['id']?.toString() ?? '') == (b['id']?.toString() ?? ''), + onChanged: (v) { + setState(() { + _brokerId = v?['id']?.toString(); + _broker.text = v != null ? _masterItemLabel(v) : ''; + }); + }, + decoratorProps: DropDownDecoratorProps( + decoration: _fieldDeco().copyWith(hintText: 'Select broker'), + baseStyle: _fieldValueStyle, + ), + popupProps: const PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + ), + ), + ), + ); + } + + Widget _buildPartnerOriginField() { + if (_isInternal) { + return _labeledField( + 'Partner', + TextFormField( + controller: _partner, + readOnly: true, + style: _fieldValueStyle, + decoration: _fieldDeco().copyWith( + hintText: 'Auto-fetched on search', + ), + ), + ); + } + return _labeledField( + 'Partner', + _uniformHeightField( + DropdownSearch>( + selectedItem: _findMasterItem(_partnerList, _partnerId), + items: (f, i) => _partnerList, + itemAsString: (v) => _masterItemLabel(v, partner: true), + compareFn: (a, b) => + (a['id']?.toString() ?? '') == (b['id']?.toString() ?? ''), + onChanged: (v) { + setState(() { + _partnerId = v?['id']?.toString(); + _partner.text = + v != null ? _masterItemLabel(v, partner: true) : ''; + }); + }, + decoratorProps: DropDownDecoratorProps( + decoration: _fieldDeco().copyWith(hintText: 'Select partner'), + baseStyle: _fieldValueStyle, + ), + popupProps: const PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + ), + ), + ), + ); + } + + Map _sharedClaimFields() { + final fields = {}; + _putField(fields, 'policy_from', _policyFrom); + _putField(fields, 'reg_no', _vehicleNoForApi()); + _putField(fields, 'policy_number', _policyNum.text); + _putField(fields, 'claim_number', _claimsNumber.text); + _putField(fields, 'claim_type', _selectedClaimTypeId); + _putField(fields, 'claim_description', _claimDesc.text); + _putField(fields, 'remark', _remark.text); + _putField(fields, 'claim_status_id', _selectedClaimStatusId); + _putField(fields, 'insured_name', _insuredName.text); + _putField(fields, 'mobile', _mobile); + _putField(fields, 'mail', _mail); + _putField(fields, 'manager_id', widget.managerId?.toString()); + _putField(fields, 'agent_id', _agentIdFromPartner()); + _putField(fields, 'insurer_id', _insurerId); + _putField(fields, 'broker_id', _brokerId); + _putField( + fields, + 'date_of_incident', + _formatDateOfIncidentForApi(_accidentDt.text), + ); + _putField(fields, 'place_of_incident', _accidentPlace.text); + _putField(fields, 'spot_surveyor_name', _spotSurveyorName.text); + _putField(fields, 'spot_surveyor_contact', _spotSurveyorContact.text); + _putField(fields, 'workshop_surveyor_name', _workshopSurveyorName.text); + _putField( + fields, + 'workshop_surveyor_contact', + _workshopSurveyorContact.text, + ); + if (_isEdit && _removedExistingFileIds.isNotEmpty) { + _putField( + fields, + 'removed_file_ids', + _removedExistingFileIds.join(','), + ); + } + return fields; + } + + Map _buildCreateClaimFields() { + final fields = _sharedClaimFields(); + _putField(fields, 'created_by', widget.userId?.toString()); + return fields; + } + + Map _buildUpdateClaimFields() { + final fields = _sharedClaimFields(); + _putField(fields, 'id', _claimId); + _putField(fields, 'updated_by', widget.userId?.toString()); + return fields; + } + + int _attachClaimFiles(http.MultipartRequest request) { + var attached = 0; + var skippedNoBytes = 0; + + for (final file in _pickedFiles) { + if (file.bytes == null) { + skippedNoBytes++; + debugPrint( + 'AddClaim upload: skipped "${file.name}" (no bytes loaded)', + ); + continue; + } + request.files.add( + http.MultipartFile.fromBytes( + 'claim_files[]', + file.bytes!, + filename: file.name, + ), + ); + attached++; + } + + if (skippedNoBytes > 0) { + debugPrint( + 'AddClaim upload: $skippedNoBytes picked file(s) not sent (no bytes)', + ); + } + + return attached; + } + + void _logClaimUploadRequest({ + required Uri uri, + required String path, + required Map fields, + required http.MultipartRequest request, + required int filesAttached, + }) { + debugPrint('════════ AddClaim API request ════════'); + debugPrint('POST $path'); + debugPrint('URL: $uri'); + debugPrint('Headers:'); + for (final entry in request.headers.entries) { + if (entry.key.toLowerCase() == 'authorization') { + final token = entry.value; + final preview = token.length > 24 + ? '${token.substring(0, 20)}…' + : '***'; + debugPrint(' ${entry.key}: $preview (${token.length} chars)'); + } else { + debugPrint(' ${entry.key}: ${entry.value}'); + } + } + debugPrint('Fields (${fields.length}):'); + final sortedKeys = fields.keys.toList()..sort(); + for (final key in sortedKeys) { + debugPrint(' $key: ${fields[key]}'); + } + debugPrint( + 'Files attached to multipart: $filesAttached ' + '(parts in request: ${request.files.length})', + ); + for (var i = 0; i < request.files.length; i++) { + final part = request.files[i]; + debugPrint( + ' [$i] field="${part.field}" ' + 'filename="${part.filename}" ' + 'size=${part.length} bytes', + ); + } + if (_pickedFiles.isNotEmpty) { + debugPrint('Picked files in UI (${_pickedFiles.length}):'); + for (final p in _pickedFiles) { + final bytes = p.bytes?.length ?? 0; + debugPrint( + ' - name="${p.name}" size=$bytes bytes ' + 'ext=${p.extension ?? "-"} sent=${p.bytes != null}', + ); + } + } + if (_isEdit) { + debugPrint( + 'Edit: claimId=$_claimId, ' + 'existingFiles=${_existingFiles.length}, ' + 'removedFileIds=${_removedExistingFileIds.join(",")}', + ); + } + debugPrint('══════════════════════════════════════'); + } + + Future _submit() async { + if (_isSaveClaimDisabled) return; + if (_policyNum.text.trim().isEmpty) { + ToastHelper.showErrorToast(context, 'Policy number is required'); + return; + } + if (_selectedClaimTypeId == null) { + ToastHelper.showErrorToast(context, 'Claim type is required'); + return; + } + if (_accidentPlace.text.trim().isEmpty) { + ToastHelper.showErrorToast(context, 'Place of accident is required'); + return; + } + if (!_isValidSurveyorContact(_spotSurveyorContact.text)) { + ToastHelper.showErrorToast( + context, + 'Spot surveyor contact must be exactly 10 digits', + ); + return; + } + if (!_isValidSurveyorContact(_workshopSurveyorContact.text)) { + ToastHelper.showErrorToast( + context, + 'Workshop surveyor contact must be exactly 10 digits', + ); + return; + } + if (_token == null) await _initializeToken(); + if (_token == null) { + if (!mounted) return; + ToastHelper.showErrorToast(context, 'Not logged in'); + return; + } + + if (_isEdit && (_claimId == null || _claimId!.isEmpty)) { + ToastHelper.showErrorToast(context, 'Claim id is missing'); + return; + } + + final path = _isEdit ? 'claim/updateClaim' : 'claim/createClaim'; + final uri = Uri.parse('${Env.apiUrl}$path'); + final request = http.MultipartRequest('POST', uri); + request.headers['Authorization'] = 'Bearer $_token'; + request.headers['app-signature'] = Env.App_Signature; + + final payload = _isEdit ? _buildUpdateClaimFields() : _buildCreateClaimFields(); + payload.forEach((key, value) { + request.fields[key] = value; + }); + final filesSent = _attachClaimFiles(request); + _logClaimUploadRequest( + uri: uri, + path: path, + fields: payload, + request: request, + filesAttached: filesSent, + ); + + setState(() => _loading = true); + try { + final streamed = await request.send(); + final response = await http.Response.fromStream(streamed); + if (response.statusCode == 200 || response.statusCode == 201) { + if (mounted) { + ToastHelper.showSuccessToast(context, 'Saved successfully'); + Navigator.of(context).pop(); + widget.onSuccess(); + } + } else if (response.statusCode == 403) { + await _api.clearLocalStorageAndRedirect(); + } else { + if (mounted) ToastHelper.showErrorToast(context, 'Failed to save'); + } + } catch (e) { + if (mounted) ToastHelper.showErrorToast(context, 'Error: $e'); + } finally { + if (mounted) setState(() => _loading = false); + } + } + + bool _canGoNextFromStep(int s) { + switch (s) { + case 0: + return _policyNum.text.trim().isNotEmpty; + case 1: + return _selectedClaimTypeId != null && + _accidentDt.text.trim().isNotEmpty && + _accidentPlace.text.trim().isNotEmpty && + _isValidSurveyorContact(_spotSurveyorContact.text) && + _isValidSurveyorContact(_workshopSurveyorContact.text); + case 2: + return true; + case 3: + return true; + default: + return false; + } + } + + void _goNext() { + if (_step == 1) { + if (!_isValidSurveyorContact(_spotSurveyorContact.text)) { + ToastHelper.showErrorToast( + context, + 'Spot surveyor contact must be exactly 10 digits', + ); + return; + } + if (!_isValidSurveyorContact(_workshopSurveyorContact.text)) { + ToastHelper.showErrorToast( + context, + 'Workshop surveyor contact must be exactly 10 digits', + ); + return; + } + } + if (!_canGoNextFromStep(_step)) { + ToastHelper.showErrorToast(context, 'Please complete required fields'); + return; + } + setState(() => _step = (_step + 1).clamp(0, 3)); + } + + void _goBack() { + setState(() => _step = (_step - 1).clamp(0, 3)); + } + + @override + void dispose() { + _vehicleSearch.dispose(); + _externalVehicleNo.dispose(); + _policySearch.dispose(); + _policyNum.dispose(); + _claimsNumber.dispose(); + _insuredName.dispose(); + _accidentDt.dispose(); + _accidentPlace.dispose(); + _claimDesc.dispose(); + _spotSurveyorName.dispose(); + _workshopSurveyorName.dispose(); + _spotSurveyorContact.dispose(); + _workshopSurveyorContact.dispose(); + _remark.dispose(); + _partner.dispose(); + _broker.dispose(); + _insurer.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final maxW = ResponsiveLayout.isMobile(context) + ? MediaQuery.sizeOf(context).width * 0.98 + : 720.0; + + return Dialog( + backgroundColor: Colors.white, + insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Theme( + data: _formTheme(context), + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: maxW, maxHeight: 640), + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(20, 16, 20, 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _header(), + const Divider(height: 24, color: _border), + _stepperRow(), + const Divider(height: 24, color: _border), + Expanded(child: SingleChildScrollView(child: _stepBody())), + const Divider(height: 20, color: _border), + _footerActions(), + ], + ), + ), + if (_loading) + const Positioned.fill( + child: ColoredBox( + color: Color(0x66FFFFFF), + child: Center(child: CircularProgressIndicator()), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _header() { + return Row( + children: [ + Text( + _isEdit ? 'Edit Claim' : 'Add Claim', + style: GoogleFonts.inter( + fontSize: 18, + fontWeight: FontWeight.w700, + color: const Color(0xFF1E293B), + ), + ), + const Spacer(), + IconButton( + onPressed: () => Navigator.pop(context), + style: IconButton.styleFrom( + backgroundColor: const Color(0xFFF1F5F9), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + icon: const Icon(Icons.close, size: 18), + ), + ], + ); + } + + Widget _stepperRow() { + const steps = [ + 'Search Policy', + 'Claim Details', + 'Upload & Remarks', + 'Review & Save', + ]; + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + for (var i = 0; i < steps.length; i++) ...[ + _stepChip(i, steps[i]), + if (i < steps.length - 1) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Icon( + Icons.chevron_right, + size: 16, + color: i < _step ? _teal : _muted, + ), + ), + ], + ], + ), + ); + } + + Widget _stepChip(int index, String label) { + final done = index < _step; + final active = index == _step; + final Color fg = done || active ? _teal : _muted; + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 26, + height: 26, + alignment: Alignment.center, + decoration: BoxDecoration( + color: done || active ? _teal : const Color(0xFFE2E8F0), + shape: BoxShape.circle, + ), + child: done + ? const Icon(Icons.check, size: 16, color: Colors.white) + : Text( + '${index + 1}', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: active ? Colors.white : _muted, + ), + ), + ), + const SizedBox(width: 8), + Text( + label, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: fg, + ), + ), + ], + ); + } + + Widget _sectionTitle(String title) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + children: [ + Container( + width: 4, + height: 18, + decoration: BoxDecoration( + color: _teal, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + Text( + title, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w700, + letterSpacing: 0.6, + color: _teal, + ), + ), + ], + ), + ); + } + + Widget _stepBody() { + switch (_step) { + case 0: + return _stepSearchPolicy(); + case 1: + return _stepClaimDetails(); + case 2: + return _stepUpload(); + case 3: + return _stepStatus(); + default: + return const SizedBox.shrink(); + } + } + + Widget _stepSearchPolicy() { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (_isInternal) ...[ + _sectionTitle('SEARCH POLICY'), + LayoutBuilder( + builder: (context, c) { + final twoCol = c.maxWidth > 520; + if (twoCol) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: _searchFieldColumn( + label: 'Search By Vehicle Number', + controller: _vehicleSearch, + hits: _vehicleHits, + fromVehicle: true, + onSearch: _runVehicleSearch, + ), + ), + const SizedBox(width: 16), + Expanded( + child: _searchFieldColumn( + label: 'Search By Policy Number', + controller: _policySearch, + hits: _policyHits, + fromVehicle: false, + onSearch: _runPolicySearch, + ), + ), + ], + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _searchFieldColumn( + label: 'Search By Vehicle Number', + controller: _vehicleSearch, + hits: _vehicleHits, + fromVehicle: true, + onSearch: _runVehicleSearch, + ), + const SizedBox(height: 12), + _searchFieldColumn( + label: 'Search By Policy Number', + controller: _policySearch, + hits: _policyHits, + fromVehicle: false, + onSearch: _runPolicySearch, + ), + ], + ); + }, + ), + const SizedBox(height: 20), + const Divider(color: _border), + const SizedBox(height: 16), + ], + _sectionTitle('CLAIM ORIGIN'), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Policy From *', style: _labelStyle), + const SizedBox(height: 6), + _uniformHeightField( + DropdownButtonFormField( + key: ValueKey('policy-from-$_policyFrom'), + initialValue: _policyFrom, + isExpanded: true, + iconSize: 20, + style: _fieldValueStyle, + decoration: _fieldDeco(), + items: _policyFromOptions + .map( + (e) => DropdownMenuItem( + value: e, + child: Text(e, style: _fieldValueStyle), + ), + ) + .toList(), + onChanged: _onPolicyFromChanged, + ), + ), + ], + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Insured Name', style: _labelStyle), + const SizedBox(height: 6), + _uniformHeightField( + TextFormField( + controller: _insuredName, + readOnly: _isInternal, + style: _fieldValueStyle, + decoration: _fieldDeco().copyWith( + hintText: _isInternal + ? 'Auto-fetched on search' + : 'Enter insured name', + ), + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + LayoutBuilder( + builder: (context, c) { + final twoCol = c.maxWidth > 520; + final policyField = _labeledField( + 'Policy Number *', + TextFormField( + controller: _policyNum, + readOnly: _isInternal, + style: _fieldValueStyle, + decoration: _fieldDeco().copyWith( + hintText: _isInternal + ? 'Auto-fetched on search' + : 'Enter policy number', + ), + validator: (v) => Validators.requiredField(v, 'Policy number'), + ), + ); + final claimsNumberField = _labeledField( + 'Claims Number', + TextFormField( + controller: _claimsNumber, + style: _fieldValueStyle, + decoration: _fieldDeco().copyWith( + hintText: 'Enter claims number', + ), + ), + ); + final vehicleNoField = _isExternal + ? _labeledField( + 'Vehicle No', + TextFormField( + controller: _externalVehicleNo, + style: _fieldValueStyle, + decoration: _fieldDeco().copyWith( + hintText: 'Enter vehicle number', + ), + textCapitalization: TextCapitalization.characters, + ), + ) + : null; + final insurerField = _buildInsurerOriginField(); + final brokerField = _buildBrokerOriginField(); + final partnerField = _buildPartnerOriginField(); + if (twoCol) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: policyField), + const SizedBox(width: 16), + Expanded(child: claimsNumberField), + ], + ), + const SizedBox(height: 12), + if (vehicleNoField != null) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: vehicleNoField), + const SizedBox(width: 16), + Expanded(child: insurerField), + ], + ) + else + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: insurerField), + const SizedBox(width: 16), + Expanded(child: brokerField), + ], + ), + const SizedBox(height: 12), + if (vehicleNoField != null) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: brokerField), + const SizedBox(width: 16), + Expanded(child: partnerField), + ], + ) + else + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: partnerField), + const Spacer(), + ], + ), + ], + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + policyField, + const SizedBox(height: 12), + claimsNumberField, + const SizedBox(height: 12), + if (vehicleNoField != null) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: vehicleNoField), + const SizedBox(width: 16), + Expanded(child: insurerField), + ], + ) + else + insurerField, + const SizedBox(height: 12), + brokerField, + const SizedBox(height: 12), + partnerField, + ], + ); + }, + ), + ], + ); + } + + Widget _searchFieldColumn({ + required String label, + required TextEditingController controller, + required List> hits, + required bool fromVehicle, + required Future Function([String? query]) onSearch, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text(label, style: _labelStyle), + const SizedBox(height: 6), + _buildSearchBox( + controller: controller, + hint: label, + onChanged: (val) async { + if (val.trim().isNotEmpty) { + await onSearch(val); + } else if (mounted) { + setState(() { + if (fromVehicle) { + _vehicleHits = []; + } else { + _policyHits = []; + } + }); + } + }, + onSearchTap: () => onSearch(), + ), + if (hits.isNotEmpty) + PolicySearchResultsList( + rows: hits, + fromVehicle: fromVehicle, + onSelected: (row) => _applyPolicySelection(row, fromVehicle: fromVehicle), + ), + ], + ); + } + + /// Search input with teal search button — matches [endorsomentUpdateValidation]. + Widget _buildSearchBox({ + required TextEditingController controller, + required String hint, + required ValueChanged onChanged, + required VoidCallback onSearchTap, + }) { + return Container( + height: 40, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: _border), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: controller, + onChanged: onChanged, + onSubmitted: (_) => onSearchTap(), + style: _fieldValueStyle, + decoration: InputDecoration( + hintText: hint, + hintStyle: _fieldHintStyle, + filled: false, + isDense: true, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 12, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 6), + child: Material( + color: _teal, + borderRadius: BorderRadius.circular(8), + child: InkWell( + onTap: _searchLoading ? null : onSearchTap, + borderRadius: BorderRadius.circular(8), + child: SizedBox( + width: 32, + height: 32, + child: _searchLoading + ? const Padding( + padding: EdgeInsets.all(8), + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon( + Icons.search, + color: Colors.white, + size: 18, + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _stepClaimDetails() { + Map? selectedType; + for (final m in _claimTypes) { + if (m['id'].toString() == _selectedClaimTypeId) { + selectedType = m; + break; + } + } + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _sectionTitle('CLAIM DETAILS'), + LayoutBuilder( + builder: (context, c) { + final wide = c.maxWidth > 520; + Widget gridRow(List children) { + if (!wide) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: children + .map((w) => Padding( + padding: const EdgeInsets.only(bottom: 12), + child: w, + )) + .toList(), + ); + } + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: children[0]), + const SizedBox(width: 16), + Expanded(child: children[1]), + ], + ); + } + + return Column( + children: [ + gridRow([ + _labeledField( + 'Claim Type *', + DropdownSearch>( + selectedItem: selectedType, + items: (f, i) => _claimTypes, + itemAsString: _claimTypeLabel, + compareFn: (a, b) => + (a['id']?.toString() ?? '') == + (b['id']?.toString() ?? ''), + onChanged: (v) { + setState(() { + _selectedClaimTypeId = v?['id']?.toString(); + }); + }, + decoratorProps: DropDownDecoratorProps( + decoration: _fieldDeco().copyWith( + hintText: 'Select claim type', + ), + baseStyle: _fieldValueStyle, + ), + popupProps: const PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + ), + ), + ), + _labeledField( + 'Date of Accident *', + TextFormField( + controller: _accidentDt, + readOnly: true, + style: _fieldValueStyle, + onTap: _pickAccidentDate, + decoration: _fieldDeco().copyWith( + hintText: 'Select date', + suffixIcon: IconButton( + tooltip: 'Pick date', + icon: const Icon( + Icons.event_outlined, + size: 18, + color: _muted, + ), + onPressed: _pickAccidentDate, + ), + suffixIconConstraints: const BoxConstraints( + minWidth: 36, + minHeight: _fieldHeight, + ), + ), + ), + ), + ]), + const SizedBox(height: 10), + gridRow([ + _labeledField( + 'Place of Accident *', + TextFormField( + controller: _accidentPlace, + style: _fieldValueStyle, + decoration: _fieldDeco(), + ), + ), + _buildClaimStatusField(), + ]), + ], + ); + }, + ), + const SizedBox(height: 10), + _labeledField( + 'Claim Description', + TextFormField( + controller: _claimDesc, + style: _fieldValueStyle, + minLines: 3, + maxLines: 5, + decoration: _fieldDeco().copyWith( + constraints: const BoxConstraints(minHeight: 80), + ), + ), + uniformHeight: false, + ), + const SizedBox(height: 20), + _sectionTitle('SURVEYORS'), + LayoutBuilder( + builder: (context, c) { + final wide = c.maxWidth > 520; + Widget pair(Widget a, Widget b) { + if (!wide) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: a, + ), + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: b, + ), + ], + ); + } + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: a), + const SizedBox(width: 16), + Expanded(child: b), + ], + ); + } + return Column( + children: [ + pair( + _labeledField( + 'Spot Surveyor Name', + TextFormField( + controller: _spotSurveyorName, + style: _fieldValueStyle, + decoration: _fieldDeco(), + ), + ), + _labeledField( + 'Workshop Surveyor Name', + TextFormField( + controller: _workshopSurveyorName, + style: _fieldValueStyle, + decoration: _fieldDeco(), + ), + ), + ), + SizedBox(height: 10), + pair( + _labeledField( + 'Spot Surveyor Contact', + _surveyorContactField(_spotSurveyorContact), + ), + _labeledField( + 'Workshop Surveyor Contact', + _surveyorContactField(_workshopSurveyorContact), + ), + ), + SizedBox(height: 10), + ], + ); + }, + ), + ], + ); + } + + Widget _stepUpload() { + final cards = []; + for (var i = 0; i < _existingFiles.length && cards.length < _maxFileSlots; i++) { + cards.add(_existingFileCard(i)); + } + for (var i = 0; i < _pickedFiles.length && cards.length < _maxFileSlots; i++) { + cards.add(_pickedFileCard(i)); + } + while (cards.length < _maxFileSlots) { + cards.add(_emptyUploadCard(cards.length + 1)); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _sectionTitle('DOCUMENTS & REMARKS'), + GridView.count( + crossAxisCount: 3, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + mainAxisSpacing: 10, + crossAxisSpacing: 10, + childAspectRatio: 1.25, + children: cards, + ), + const SizedBox(height: 16), + _labeledField( + 'Remark', + TextFormField( + controller: _remark, + style: _fieldValueStyle, + minLines: 3, + maxLines: 5, + decoration: _fieldDeco().copyWith( + hintText: 'Add a note...', + constraints: const BoxConstraints(minHeight: 80), + ), + ), + uniformHeight: false, + ), + ], + ); + } + + String _existingFileName(Map file) { + final candidates = [ + 'file_name', + 'fileName', + 'name', + 'original_name', + 'original_file_name', + ]; + for (final k in candidates) { + final v = file[k]; + if (v != null && v.toString().trim().isNotEmpty) { + final raw = v.toString().trim(); + return raw.split('/').last; + } + } + return 'Document'; + } + + Widget _existingFileCard(int index) { + final file = _existingFiles[index]; + final name = _existingFileName(file); + return _fileCard( + name: name, + onRemove: () => _removeExistingFile(index), + onDownload: () => _downloadExistingFile(file), + iconColor: _teal, + labelColor: _teal, + ); + } + + Widget _pickedFileCard(int index) { + final file = _pickedFiles[index]; + return _fileCard( + name: file.name, + onRemove: () => _removePickedFile(index), + onDownload: () => _downloadPickedFile(file), + iconColor: _teal, + labelColor: _teal, + ); + } + + Widget _fileCard({ + required String name, + required VoidCallback onRemove, + VoidCallback? onDownload, + required Color iconColor, + required Color labelColor, + }) { + return Container( + padding: const EdgeInsets.fromLTRB(8, 8, 4, 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: _teal), + color: const Color(0xFFE8F7F5), + ), + child: Stack( + children: [ + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.insert_drive_file_outlined, color: iconColor), + const SizedBox(height: 4), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Text( + name, + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.inter( + fontSize: 11, + color: labelColor, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Positioned( + top: 0, + right: 0, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (onDownload != null) + Material( + color: Colors.transparent, + child: InkWell( + onTap: onDownload, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.all(2), + child: const Icon( + Icons.download_rounded, + size: 18, + color: Color(0xFF1565C0), + ), + ), + ), + ), + Material( + color: Colors.transparent, + child: InkWell( + onTap: onRemove, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.all(2), + child: const Icon( + Icons.cancel, + size: 18, + color: Color(0xFFC62828), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _emptyUploadCard(int slotNumber) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: _pickFile, + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFFCBD5E1)), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.upload_file_outlined, color: _muted), + const SizedBox(height: 4), + Text( + 'Upload $slotNumber', + textAlign: TextAlign.center, + style: GoogleFonts.inter(fontSize: 11, color: _muted), + ), + ], + ), + ), + ), + ); + } + + Widget _stepStatus() { + String? typeLabel; + if (_selectedClaimTypeId != null) { + for (final e in _claimTypes) { + if (e['id']?.toString() == _selectedClaimTypeId) { + typeLabel = _claimTypeLabel(e); + break; + } + } + } + final vn = _isExternal + ? (_externalVehicleNo.text.trim().isNotEmpty + ? _externalVehicleNo.text.trim() + : '—') + : (_vehicleSearch.text.trim().isNotEmpty + ? _vehicleSearch.text.trim() + : '—'); + final pn = + _policyNum.text.trim().isNotEmpty ? _policyNum.text.trim() : '—'; + final statusLabel = + _statusLabelForId(_selectedClaimStatusId) ?? '—'; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _sectionTitle('REVIEW'), + Container( + width: double.infinity, + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: const Color(0xFFE8F7F5), + borderRadius: BorderRadius.circular(10), + ), + child: Text.rich( + TextSpan( + style: GoogleFonts.inter( + fontSize: 12, + color: const Color(0xFF334155), + ), + children: [ + TextSpan( + text: 'Review: ', + style: GoogleFonts.inter( + fontWeight: FontWeight.w700, + fontSize: 12, + color: const Color(0xFF334155), + ), + ), + TextSpan( + text: + 'Vehicle $vn · Policy $pn · ${typeLabel ?? "—"} · Status ', + ), + TextSpan( + text: statusLabel, + style: GoogleFonts.inter( + fontWeight: FontWeight.w700, + fontSize: 12, + color: const Color(0xFF334155), + ), + ), + ], + ), + ), + ), + ], + ); + } + + TextStyle get _labelStyle => GoogleFonts.inter( + fontSize: 12, + color: _muted, + fontWeight: FontWeight.w500, + ); + + TextStyle get _fieldValueStyle => GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w400, + color: const Color(0xFF0F172A), + ); + + TextStyle get _fieldHintStyle => GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w400, + color: const Color(0xFF94A3B8), + ); + + ThemeData _formTheme(BuildContext context) { + final base = GoogleFonts.interTextTheme(Theme.of(context).textTheme); + return Theme.of(context).copyWith( + textTheme: base.copyWith( + bodyLarge: _fieldValueStyle, + bodyMedium: _fieldValueStyle, + titleMedium: _fieldValueStyle, + labelLarge: _fieldValueStyle, + ), + inputDecorationTheme: InputDecorationTheme( + isDense: true, + filled: true, + fillColor: Colors.white, + hintStyle: _fieldHintStyle, + constraints: const BoxConstraints(minHeight: _fieldHeight), + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 11), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _teal, width: 1.5), + ), + ), + ); + } + + Widget _uniformHeightField(Widget child) { + return SizedBox( + height: _fieldHeight, + child: child, + ); + } + + InputDecoration _fieldDeco() { + return InputDecoration( + isDense: true, + filled: true, + fillColor: Colors.white, + hintStyle: _fieldHintStyle, + constraints: const BoxConstraints(minHeight: _fieldHeight), + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 11), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _teal, width: 1.5), + ), + ); + } + + static final RegExp _tenDigitContact = RegExp(r'^[0-9]{10}$'); + + static String _contactDigitsOnly(String raw) { + final digits = raw.replaceAll(RegExp(r'\D'), ''); + if (digits.length <= 10) return digits; + return digits.substring(0, 10); + } + + bool _isValidSurveyorContact(String? value) { + final v = value?.trim() ?? ''; + if (v.isEmpty) return true; + return _tenDigitContact.hasMatch(v); + } + + Widget _buildClaimStatusField() { + return _labeledField( + 'Claim Status', + DropdownButtonFormField( + key: ValueKey('claim-status-$_selectedClaimStatusId'), + initialValue: _selectedClaimStatusId, + isExpanded: true, + iconSize: 20, + style: _fieldValueStyle, + decoration: _fieldDeco(), + items: _claimStatuses + .map((m) { + final id = m['id']?.toString() ?? ''; + final label = _claimStatusLabel(m) ?? id; + return DropdownMenuItem( + value: id.isEmpty ? null : id, + child: Text(label, style: _fieldValueStyle), + ); + }) + .where((e) => e.value != null) + .toList(), + onChanged: (v) => setState(() => _selectedClaimStatusId = v), + ), + ); + } + + Widget _surveyorContactField(TextEditingController controller) { + return TextFormField( + controller: controller, + style: _fieldValueStyle, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(10), + ], + decoration: _fieldDeco().copyWith( + hintText: '10-digit number', + ), + validator: (v) => Validators.nonReqphone(v, 'Contact'), + ); + } + + Widget _labeledField( + String label, + Widget field, { + bool uniformHeight = true, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: _labelStyle), + const SizedBox(height: 6), + uniformHeight ? _uniformHeightField(field) : field, + ], + ); + } + + Widget _footerActions() { + return Row( + children: [ + Text( + '* Required fields', + style: GoogleFonts.inter(fontSize: 11, color: _muted), + ), + const Spacer(), + if (_step > 0) + OutlinedButton( + onPressed: _goBack, + style: OutlinedButton.styleFrom( + foregroundColor: Colors.black87, + side: const BorderSide(color: _border), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text('Back'), + ), + const SizedBox(width: 8), + OutlinedButton( + onPressed: () => Navigator.pop(context), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.black87, + side: const BorderSide(color: _border), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text('Cancel'), + ), + const SizedBox(width: 8), + if (_step < 3) + FilledButton( + onPressed: _goNext, + style: FilledButton.styleFrom( + backgroundColor: _teal, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text('Continue'), + ) + else + FilledButton.icon( + onPressed: _isSaveClaimDisabled ? null : _submit, + icon: const Icon(Icons.save_outlined, size: 18), + label: const Text('Save Claim'), + style: FilledButton.styleFrom( + backgroundColor: _teal, + disabledBackgroundColor: const Color(0xFFCBD5E1), + disabledForegroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/screens/Enquiry/policy_claims_endros/claims.dart b/lib/presentation/screens/Enquiry/policy_claims_endros/claims.dart index 4b28e2d..779c3e2 100644 --- a/lib/presentation/screens/Enquiry/policy_claims_endros/claims.dart +++ b/lib/presentation/screens/Enquiry/policy_claims_endros/claims.dart @@ -1,44 +1,65 @@ -import 'package:flutter/cupertino.dart'; +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; +import 'package:nhance_partner/presentation/screens/Enquiry/policy_claims_endros/add_claim_stepper_dialog.dart'; import 'package:nhance_partner/presentation/providers/userRoleProvider.dart'; -import '../../../../core/routing/routes.dart'; + import '../../../../core/services/api_service.dart'; import '../../../../data/utils/Pagination.dart'; +import '../../../../data/utils/toastNotification.dart'; import '../../../layouts/main_layout.dart'; import '../../../layouts/responsive_layout.dart'; import '../../../providers/manager_provider.dart'; +import '../../../themes/indicators/date_range_picker_field.dart'; import '../../../themes/indicators/export_btn.dart'; import '../../../themes/indicators/search_field_theme.dart'; -import '../../../widgets/custom_action_popup.dart'; -import '../enquiry/policy_popup.dart'; class claimList extends ConsumerStatefulWidget { const claimList({super.key}); + @override ConsumerState createState() => claimListState(); } class claimListState extends ConsumerState { + static const Color _teal = Color(0xFF14A492); + static const Color _surfaceGrey = Color(0xFFF1F5F9); + static const Color _border = Color(0xFFE2E8F0); + static const Color _muted = Color(0xFF64748B); + static const Color _filterFocusTeal = Color(0xFF50A398); + static const Color _actionDownloadBlue = Color(0xFF1565C0); + int currentPage = 1; int itemsPerPage = 10; late ApiService apiService; - // List> dataVal = []; List> getStaffData = []; List> originalData = []; List> filteredData = []; bool isLoading = false; - final TextEditingController _searchStaffController = TextEditingController(); + final TextEditingController _quickSearchController = TextEditingController(); + final TextEditingController _startDateController = TextEditingController(); + final TextEditingController _endDateController = TextEditingController(); + final ScrollController _tableVScrollController = ScrollController(); dynamic userId; dynamic roleId; dynamic managerId; + // bool _pendingTab = true; + + List> _masterClaimTypeMaps = []; + List> _masterInsurerMaps = []; + List> _masterClaimStatusMaps = []; + + String? _filterClaimType; + String? _filterInsurer; + /// Selected claim status master id (stored in filter choice `v`). + String? _filterStatus; + @override void initState() { super.initState(); @@ -46,749 +67,2010 @@ class claimListState extends ConsumerState { apiService = ApiService(); Future.microtask(() { - final id = ref.read(managerIdProvider); managerId = ref.read(managerIdProvider); - - // roleId = ref.read(userRoleProvider); - // roleId = ref.read(userRoleProvider); roleId = ref.read(userRoleProvider); userId = ref.read(userIdProvider); - print("G47 => r : $roleId | mId: $id | uId: $userId "); + if (userId != null) { - getStaffList(managerId, userId, roleId); + loadClaimsList(); } + _loadFilterMasters(); }); } - Future getStaffList(int managerId, int userId, role) async { - print('G54 => Fns called => $managerId | $role'); - setState(() { - isLoading = true; - }); + Future _loadFilterMasters() async { + try { + final claimR = await apiService.fetchMasterDropDown('Claim'); + if ((claimR['status'] == 'success' || claimR['status'] == 200) && + claimR['data'] is List) { + if (mounted) { + setState(() { + _masterClaimTypeMaps = + List>.from(claimR['data']); + }); + } + } + final insR = await apiService.fetchMasterDropDown('Insurers'); + if ((insR['status'] == 'success' || insR['status'] == 200) && + insR['data'] is List) { + if (mounted) { + setState(() { + _masterInsurerMaps = + List>.from(insR['data']); + }); + } + } + final statusR = await apiService.fetchClaimStatusList(); + if ((statusR['status'] == 'success' || statusR['status'] == 200) && + statusR['data'] is List && + (statusR['data'] as List).isNotEmpty) { + if (mounted) { + setState(() { + _masterClaimStatusMaps = + List>.from(statusR['data']); + }); + } + } + } catch (_) {} + } + + @override + void dispose() { + _quickSearchController.dispose(); + _startDateController.dispose(); + _endDateController.dispose(); + _tableVScrollController.dispose(); + super.dispose(); + } + + String? _apiDateFromPicker(String text) { + final t = text.trim(); + if (t.isEmpty) return null; + try { + final d = DateRangePickerField.storageFormat.parse(t); + return DateFormat('yyyy-MM-dd').format(d); + } catch (_) { + return null; + } + } + + int? _claimTypeIdForFilter() { + if (_filterClaimType == null || _filterClaimType!.trim().isEmpty) { + return null; + } + return int.tryParse(_filterClaimType!.trim()); + } + + int? _insurerIdForFilter() { + if (_filterInsurer == null || _filterInsurer!.trim().isEmpty) return null; + return int.tryParse(_filterInsurer!.trim()); + } + + int? _claimStatusIdForFilter() { + if (_filterStatus == null || _filterStatus!.trim().isEmpty) return null; + return int.tryParse(_filterStatus!.trim()); + } + + Future loadClaimsList() async { + if (!mounted) return; + setState(() => isLoading = true); try { - final response = await apiService.fetchClaimList(managerId, userId, role); + final role = roleId?.toString().toLowerCase() ?? ''; + final mid = int.tryParse(managerId?.toString() ?? ''); + final uid = int.tryParse(userId?.toString() ?? ''); - if (response['status'] == 'success') { + final response = await apiService.fetchClaims( + managerId: (role == 'manager' || role == 'accounts') ? mid : null, + agentId: role == 'agent' ? uid : null, + handlerId: role == 'handler' ? uid : null, + staffId: (role != 'manager' && + role != 'accounts' && + role != 'agent' && + role != 'handler') + ? uid + : null, + claimTypeId: _claimTypeIdForFilter(), + claimStatusId: _claimStatusIdForFilter(), + insurerId: _insurerIdForFilter(), + fromDate: _apiDateFromPicker(_startDateController.text), + toDate: _apiDateFromPicker(_endDateController.text), + ); + + if (!mounted) return; + + if (response['status'] == 'success' || + response['status'] == 200 || + response['status'] == '200') { final data = response['data']; - print('F64 => clImlist => ${response['data']}'); setState(() { if (data is List) { - // Already a list of maps - getStaffData = List>.from(data); + getStaffData = data + .whereType() + .map((e) => Map.from(e)) + .toList(); } else if (data is Map) { - // Single object, wrap in a list getStaffData = [Map.from(data)]; } else { getStaffData = []; } - // getStaffData = List>.from(response['data']); - originalData = getStaffData; - filteredData = List.from(originalData); - // print('originalData - $getClaimPolicies'); + originalData = List>.from(getStaffData); + _rebuildFiltered(); }); } else { - getStaffData = []; - originalData = []; + setState(() { + getStaffData = []; + originalData = []; + filteredData = []; + }); } } catch (e) { - print('Exception occurred: $e'); + debugPrint('Claims list error: $e'); + if (mounted) { + setState(() { + getStaffData = []; + originalData = []; + filteredData = []; + }); + } } finally { - setState(() { - isLoading = false; - }); + if (mounted) setState(() => isLoading = false); } } - List get _paginatedData { - // Sort descending by id first - final sortedData = [...filteredData] - ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); - - if (sortedData.isEmpty) return []; - - // Ensure currentPage is valid - final maxPage = (sortedData.length / itemsPerPage).ceil(); - final safePage = currentPage.clamp(1, maxPage); - - final startIndex = (safePage - 1) * itemsPerPage; - final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length); - - return sortedData.sublist(startIndex, endIndex); + static String _pickStr(Map row, List keys) { + for (final k in keys) { + final v = row[k]; + if (v != null && v.toString().trim().isNotEmpty) { + return v.toString(); + } + } + return '-'; } - void filterData(String query) { - print("FilterDAta - $query"); - setState(() { - filteredData = getStaffData.where((item) { - // final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive"; + // bool _isSettled(Map item) { + // final raw = _pickStr(item, ['claim_status_id']); + // final id = int.tryParse(raw); + // return id == 65; + // } - return (item['reg_no'] ?? '-').toLowerCase().contains( - query.toLowerCase(), - ) || - (item['insurer_short_name'] ?? '-').toLowerCase().contains( - query.toLowerCase(), - ) || - (item['policy_no'] ?? '-').toLowerCase().contains( - query.toLowerCase(), - ) || - (item['claim_type_value'] ?? '-').toLowerCase().contains( - query.toLowerCase(), - ) || - (item['claim_number'] ?? '-').toLowerCase().contains( - query.toLowerCase(), - ) || - (item['claim_status_value'] ?? '-').toLowerCase().contains( - query.toLowerCase(), - ) || - (_formatDate( - item['policy_end_date'] ?? '-', - ).toLowerCase()).contains(query.toLowerCase()); - }).toList(); + // bool _matchesTab(Map item) { + // return _pendingTab ? !_isSettled(item) : _isSettled(item); + // } + + DateTime? _parseCreated(Map item) { + final raw = _pickStr(item, [ + 'created_at', + 'created_date', + 'created_on', + 'inserted_at', + ]); + if (raw == '-') return null; + try { + return DateTime.parse(raw); + } catch (_) { + return null; + } + } + + void _addSearchText(List out, String? value) { + if (value == null) return; + final s = value.trim(); + if (s.isEmpty || s == '-') return; + out.add(s); + } + + void _addSearchDate(List out, Map item, List keys) { + final raw = _pickStr(item, keys); + if (raw == '-') return; + _addSearchText(out, raw); + _addSearchText(out, _formatDate(raw)); + } + + List _searchableTexts(Map item) { + final texts = []; + + for (final value in item.values) { + _addSearchText(texts, value?.toString()); + } + + _addSearchText(texts, _pickStr(item, ['policy_no', 'policy_number'])); + _addSearchText(texts, _pickStr(item, ['claim_number'])); + _addSearchText(texts, _pickStr(item, ['broker_name', 'broker'])); + _addSearchText( + texts, + _pickStr(item, ['agent_name', 'partner_name', 'partner']), + ); + _addSearchDate(texts, item, [ + 'date_of_incident', + 'accident_date', + 'accident_datetime', + 'date_of_accident', + ]); + _addSearchDate(texts, item, [ + 'created_at', + 'created_date', + 'created_on', + 'inserted_at', + ]); + + return texts; + } + + bool _matchesQuickSearch(Map item, String query) { + if (query.isEmpty) return true; + final q = query.toLowerCase(); + return _searchableTexts(item).any((t) => t.toLowerCase().contains(q)); + } + + void _rebuildFiltered() { + final q = _quickSearchController.text.trim(); + filteredData = originalData.where((item) { + return _matchesQuickSearch(item, q); + }).toList(); + } + + void _applyFilterBar() { + setState(() => currentPage = 1); + loadClaimsList(); + } + + void _onQuickSearch(String _) { + setState(() { + _rebuildFiltered(); + currentPage = 1; }); } - String _formatDate(String rawDate) { + // void _setTab(bool pending) { + // if (_pendingTab == pending) return; + // setState(() { + // _pendingTab = pending; + // _rebuildFiltered(); + // currentPage = 1; + // }); + // } + + // int get _pendingCount => + // originalData.where((e) => !_isSettled(e)).length; + + // int get _settledCount => originalData.where(_isSettled).length; + + bool get _canShowDeleteAction { + final role = roleId?.toString().trim().toLowerCase() ?? ''; + return role == 'manager' || role == 'accounts' || role == 'account'; + } + + void _confirmDelete(BuildContext context, String id) { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: Row( + children: [ + Icon(Icons.warning_amber_rounded, color: Colors.red.shade600, size: 22), + const SizedBox(width: 8), + Text( + 'Delete Claim', + style: GoogleFonts.poppins(fontSize: 15, fontWeight: FontWeight.w600), + ), + ], + ), + content: Text( + 'Are you sure you want to delete this claim?\nThis action cannot be undone.', + style: GoogleFonts.poppins(fontSize: 13, color: Colors.grey.shade700), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text( + 'Cancel', + style: GoogleFonts.poppins(fontSize: 13, color: Colors.grey.shade600), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red.shade600, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + onPressed: () async { + Navigator.pop(ctx); + await _deleteClaim(id); + }, + child: Text( + 'Delete', + style: GoogleFonts.poppins(fontSize: 13, color: Colors.white), + ), + ), + ], + ), + ); + } + + Future _deleteClaim(String id) async { + if (id.isEmpty || id == '-') { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Invalid claim id.', + style: GoogleFonts.poppins(fontSize: 13), + ), + backgroundColor: Colors.red.shade600, + behavior: SnackBarBehavior.floating, + ), + ); + return; + } + + final updatedBy = userId?.toString().trim(); + if (updatedBy == null || updatedBy.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'User id not available.', + style: GoogleFonts.poppins(fontSize: 13), + ), + backgroundColor: Colors.red.shade600, + behavior: SnackBarBehavior.floating, + ), + ); + return; + } + try { - final dateTime = DateTime.parse(rawDate); - return DateFormat('dd-MM-yyyy').format(dateTime); // date only + setState(() => isLoading = true); + + final response = await apiService.deleteClaim( + id: id, + updatedBy: updatedBy, + ); + + if (response['status'] == 'success' || response['code'] == 200) { + refresh(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Claim deleted successfully.', + style: GoogleFonts.poppins(fontSize: 13), + ), + backgroundColor: Colors.green.shade600, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + duration: const Duration(seconds: 2), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + response['message']?.toString() ?? 'Failed to delete claim.', + style: GoogleFonts.poppins(fontSize: 13), + ), + backgroundColor: Colors.red.shade600, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + duration: const Duration(seconds: 2), + ), + ); + } } catch (e) { - return rawDate; // fallback if parsing fails + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error: $e', style: GoogleFonts.poppins(fontSize: 13)), + backgroundColor: Colors.red.shade600, + behavior: SnackBarBehavior.floating, + ), + ); + } finally { + if (mounted) setState(() => isLoading = false); } } - List _buildPopupMenuActions(BuildContext context, dynamic data) { - return [ - GestureDetector( - onTap: () { - Navigator.pop(context); - print('EDITStaff - ${data['id']}'); - dynamic id = data['id']; - // context.go('/staff/$id'); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.edit_sharp, color: Color(0xFF319718), size: 18), - SizedBox(width: 10), - Text('Edit'), - ], - ), - ), + List _uniqueFrom(String Function(Map) pick) { + final s = {}; + for (final r in originalData) { + final v = pick(r); + if (v != '-') s.add(v); + } + return s.toList()..sort(); + } + + List> get _paginatedSorted { + final sorted = [...filteredData] + ..sort((a, b) { + final ida = int.tryParse(_pickStr(a, ['id'])) ?? 0; + final idb = int.tryParse(_pickStr(b, ['id'])) ?? 0; + return idb.compareTo(ida); + }); + if (sorted.isEmpty) return []; + final maxPage = (sorted.length / itemsPerPage).ceil(); + final safePage = currentPage.clamp(1, maxPage); + final start = (safePage - 1) * itemsPerPage; + final end = (start + itemsPerPage).clamp(0, sorted.length); + return sorted.sublist(start, end); + } + + DateTime? _parseClaimDateTime(String raw) { + final t = raw.trim(); + if (t.isEmpty || t == '-') return null; + final patterns = [ + DateFormat('dd-MM-yyyy hh:mm a'), + DateFormat('dd-MM-yyyy HH:mm'), + DateFormat('dd-MM-yyyy'), + DateFormat('yyyy-MM-dd HH:mm:ss'), + DateFormat('yyyy-MM-dd'), ]; + for (final p in patterns) { + try { + return p.parse(t); + } catch (_) {} + } + try { + return DateTime.parse(t); + } catch (_) { + return null; + } + } + + String _formatDate(String rawDate) { + final dt = _parseClaimDateTime(rawDate); + if (dt != null) return DateFormat('dd-MM-yyyy').format(dt); + return rawDate; + } + + String _formatDateTime(String raw) { + final dt = _parseClaimDateTime(raw); + if (dt != null) { + return DateFormat('dd-MM-yyyy, hh:mm a').format(dt); + } + return raw; } void refresh() { - getStaffList(managerId, userId, roleId); + loadClaimsList(); + } + + void _clearFilters() { + setState(() { + _startDateController.clear(); + _endDateController.clear(); + _filterClaimType = null; + _filterInsurer = null; + _filterStatus = null; + _quickSearchController.clear(); + currentPage = 1; + }); + loadClaimsList(); + } + + InputDecoration _filterDropdownDecoration(String hintText) { + return InputDecoration( + isDense: true, + hintText: hintText, + hintStyle: GoogleFonts.inter(fontSize: 11, color: const Color(0xFF94A3B8)), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFocusTeal, width: 1.5), + ), + ); + } + + Map _filterAllItem(String label) => { + 'v': '', + 'label': label, + 'sub': '', + }; + + Map _findSelectedChoice( + List> choices, + String? value, + ) { + final v = value ?? ''; + for (final c in choices) { + if ((c['v']?.toString() ?? '') == v) return c; + } + return choices.isNotEmpty ? choices.first : _filterAllItem(''); + } + + List> _claimTypeFilterChoices(List fromData) { + final byId = >{}; + for (final m in _masterClaimTypeMaps) { + final id = (m['id'] ?? '').toString().trim(); + final label = (m['claim_type'] ?? '').toString().trim(); + if (id.isEmpty || label.isEmpty) continue; + byId[id] = {'v': id, 'label': label, 'sub': ''}; + } + for (final label in fromData) { + if (label.isEmpty) continue; + if (byId.values.any((c) => (c['label'] as String) == label)) continue; + for (final row in originalData) { + if (_pickStr(row, ['claim_type_value']) == label) { + final id = (row['claim_type_id'] ?? '').toString().trim(); + if (id.isNotEmpty) byId[id] = {'v': id, 'label': label, 'sub': ''}; + break; + } + } + } + final list = byId.values.toList() + ..sort((a, b) => (a['label'] as String).compareTo(b['label'] as String)); + return [_filterAllItem('Claim type'), ...list]; + } + + List> _statusFilterChoices(List fromData) { + final byId = >{}; + + void addStatus(String? id, String label) { + final idStr = id?.toString().trim() ?? ''; + final lab = label.trim(); + if (idStr.isEmpty || lab.isEmpty || lab == '-') return; + byId[idStr] = {'v': idStr, 'label': lab, 'sub': ''}; + } + + for (final m in _masterClaimStatusMaps) { + addStatus( + m['id']?.toString(), + (m['claim_status'] ?? + m['status'] ?? + m['name'] ?? + m['claim_status_value'] ?? + m['status_name'] ?? + '') + .toString(), + ); + } + + for (final row in originalData) { + addStatus( + row['claim_status_id']?.toString(), + _pickStr(row, ['claim_status_value']), + ); + } + + for (final label in fromData) { + if (label.isEmpty) continue; + if (byId.values.any((c) => (c['label'] as String) == label)) continue; + for (final row in originalData) { + if (_pickStr(row, ['claim_status_value']) == label) { + addStatus(row['claim_status_id']?.toString(), label); + break; + } + } + } + + final list = byId.values.toList() + ..sort((a, b) => (a['label'] as String).compareTo(b['label'] as String)); + return [_filterAllItem('Status'), ...list]; + } + + List> _insurerFilterChoices(List fromData) { + final byId = >{}; + for (final m in _masterInsurerMaps) { + final id = (m['id'] ?? '').toString().trim(); + final label = (m['insurer_short_name'] ?? + m['insurer_name'] ?? + m['name'] ?? + '') + .toString() + .trim(); + if (id.isEmpty || label.isEmpty) continue; + byId[id] = {'v': id, 'label': label, 'sub': ''}; + } + for (final label in fromData) { + if (label.isEmpty) continue; + if (byId.values.any((c) => (c['label'] as String) == label)) continue; + for (final row in originalData) { + if (_pickStr(row, ['insurer_short_name']) == label) { + final id = (row['insurer_id'] ?? '').toString().trim(); + if (id.isNotEmpty) byId[id] = {'v': id, 'label': label, 'sub': ''}; + break; + } + } + } + final list = byId.values.toList() + ..sort((a, b) => (a['label'] as String).compareTo(b['label'] as String)); + return [_filterAllItem('Select Insurer'), ...list]; + } + + InputDecoration _popupSearchDecoration(String hint) { + return InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: hint, + hintStyle: GoogleFonts.inter(fontSize: 12, color: const Color(0xFF94A3B8)), + isDense: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFocusTeal, width: 1.5), + ), + ); + } + + Widget _buildFilterDropdownSearch({ + required double width, + String? placeholder, + required String? selectedValue, + required List> choices, + required void Function(String?) onChanged, + bool twoLineItems = false, + }) { + final hint = placeholder ?? + (choices.isNotEmpty + ? (choices.first['label'] ?? '').toString() + : ''); + final selected = _findSelectedChoice(choices, selectedValue); + + return SizedBox( + height: 38, + width: width, + child: DropdownSearch>( + selectedItem: selected, + items: (filter, infiniteScrollProps) => choices, + itemAsString: (m) { + final label = (m['label'] ?? '').toString(); + final sub = (m['sub'] ?? '').toString(); + if (twoLineItems && sub.isNotEmpty) return '$label $sub'; + return label; + }, + compareFn: (a, b) => + (a['v']?.toString() ?? '') == (b['v']?.toString() ?? ''), + decoratorProps: DropDownDecoratorProps( + decoration: _filterDropdownDecoration(hint), + ), + dropdownBuilder: (context, selectedItem) { + final m = selectedItem ?? _filterAllItem(hint); + final isAll = (m['v']?.toString() ?? '').isEmpty; + final label = (m['label'] ?? hint).toString(); + return Align( + alignment: Alignment.centerLeft, + child: Text( + label, + style: GoogleFonts.inter( + fontSize: 11, + color: isAll + ? const Color(0xFF94A3B8) + : const Color(0xFF334155), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ); + }, + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: const BoxConstraints(maxHeight: 280), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + borderRadius: BorderRadius.circular(8), + ), + searchFieldProps: TextFieldProps( + decoration: _popupSearchDecoration('Search'), + ), + itemBuilder: (context, item, isDisabled, isSelected) { + final isAll = (item['v']?.toString() ?? '').isEmpty; + if (isAll) return const SizedBox.shrink(); + + final label = (item['label'] ?? '').toString(); + final sub = (item['sub'] ?? '').toString(); + final showTwoLine = + twoLineItems && !isAll && sub.isNotEmpty; + + return Container( + color: isSelected ? const Color(0xFFF1F5F9) : null, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: showTwoLine + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + label.toUpperCase(), + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w700, + color: const Color(0xFF0F172A), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (sub.isNotEmpty) ...[ + const SizedBox(height: 2), + Text( + sub, + style: GoogleFonts.inter( + fontSize: 10, + color: const Color(0xFF94A3B8), + ), + ), + ], + ], + ) + : Text( + label, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: const Color(0xFF0F172A), + ), + ), + ), + ); + }, + ), + onChanged: (val) { + final v = val?['v']?.toString() ?? ''; + onChanged(v.isEmpty ? null : v); + }, + ), + ); + } + + Map get _exportConfig => { + 'displayHeaders': const [ + 'S No', + 'Created Date', + 'Claim Type', + 'Vehicle No', + 'Insured Name', + 'Policy Number', + 'Claim Number', + 'Accident Date', + 'Insurer', + 'Broker', + 'Partner', + 'Remarks', + 'Status', + 'Pending Days', + ], + 'keys': const [ + 'sno', + 'created_at', + 'claim_type_value', + 'reg_no', + 'insured_name', + 'policy_no', + 'claim_number', + 'accident_date', + 'insurer_short_name', + 'broker', + 'partner', + 'remark', + 'claim_status_value', + 'pending_days', + ], + 'valueFormatters': { + 'created_at': (dynamic v, Map row) { + final raw = _pickStr(row, [ + 'created_at', + 'created_date', + 'created_on', + ]); + if (raw == '-') return '-'; + return _formatDate(raw); + }, + 'accident_date': (dynamic v, Map row) { + final raw = _pickStr(row, [ + 'date_of_incident', + 'accident_date', + 'accident_datetime', + 'date_of_accident', + ]); + if (raw == '-') return '-'; + return _formatDate(raw); + }, + 'insured_name': (dynamic v, Map row) => _pickStr(row, [ + 'insured_name', + 'customer_name', + 'proposer_name', + ]), + 'claim_number': (dynamic v, Map row) => + _pickStr(row, ['claim_number']), + 'broker': (dynamic v, Map row) => + _pickStr(row, ['broker_name', 'broker']), + 'partner': (dynamic v, Map row) => + _pickStr(row, ['agent_name', 'partner_name', 'partner']), + 'remark': (dynamic v, Map row) => + _pickStr(row, ['remark']), + 'pending_days': (dynamic v, Map row) => + _pendingDaysLabel(row), + 'settled_amount': (dynamic v, Map row) => _pickStr(row, [ + 'settled_amount', + 'claim_amount', + 'settlement_amount', + ]), + }, + }; + + Future _onExportClaims() async { + final cfg = _exportConfig; + try { + await ExcelExporter.exportToExcel( + sheetName: 'Claims', + data: filteredData, + displayHeaders: cfg['displayHeaders'] as List, + keys: cfg['keys'] as List, + fileName: 'claim_list', + valueFormatters: + cfg['valueFormatters'] as Map?, + ); + if (mounted) { + ToastHelper.showSuccessToast(context, 'Export finished'); + } + } catch (e) { + if (mounted) { + ToastHelper.showErrorToast(context, 'Export failed: $e'); + } + } } @override Widget build(BuildContext context) { return MainLayout( - title: "claims", + title: 'Claims', body: SelectionArea( - child: Container( - // color: Colors.yellow.shade50, - width: MediaQuery.of(context).size.width, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - // Container( - // // height: 30, - // width: MediaQuery.of(context).size.width, - // child: GestureDetector( - // onTap: () { - // context.go(AppRoutes.dashboard); - // }, - // child: Row( - // crossAxisAlignment: CrossAxisAlignment.center, - // mainAxisAlignment: MainAxisAlignment.start, - // children: [ - // Tooltip( - // message: 'Back', - // child: IconButton( - // icon: const Icon( - // Icons.arrow_left_sharp, - // size: 25, - // color: Color(0xFF425B5B), - // ), - // onPressed: () { - // context.go(AppRoutes.dashboard); - // }, - // splashRadius: 18, - // hoverColor: Colors.black12, - // padding: const EdgeInsets.all(4), - // constraints: const BoxConstraints(), - // ), - // ), - // const SizedBox(width: 5), // spacing between icon and text - // Text( - // "Claims", - // style: GoogleFonts.poppins( - // fontSize: 12, - // fontWeight: FontWeight.w500, - // ), - // ), - // ], - // ), - // ), - // ), - // - // SizedBox(height: 5), - Expanded( - child: Container( - // color: Colors.green, - // color: Colors.green.shade50, - width: MediaQuery.of(context).size.width, - - padding: EdgeInsets.all(8.0), - child: Column( - children: [ - Container( - // height: 40, - // color: Colors.pink, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Claims", - style: GoogleFonts.poppins( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - Spacer(), - - ThemedSearchField( - hintText: 'Search', - backgroundColor: Color(0xFFFFFFFF), - txtHeight: 30, - // backgroundColor: Color(0xFFF6F8F8), - onChanged: filterData, - controller: _searchStaffController, - txtwidth: ResponsiveLayout.isMobile(context) - ? MediaQuery.of(context).size.width * 0.6 - : MediaQuery.of(context).size.width * 0.15, - ), - SizedBox(width: 10), - ExportBtn( - sheetName: "Claims", - fileName: "claim_list", - data: filteredData, - txt: !ResponsiveLayout.isMobile(context) - ? true - : false, - displayHeaders: [ - "S.No.", - "Vehicle.No.", - "Insurer", - "Policy No.", - "Policy Expiry Date", - "Claim Type", - "Claim No.", - "Status", - ], - keys: [ - "sno", // handled internally as i + 1 - "reg_no", - "insurer_short_name", - "policy_no", - "policy_end_date", - "claim_number", - "claim_type_value", - 'claim_status_value', - ], - ), - SizedBox(width: 10), - InkWell( - onTap: () { - showDialog( - context: context, - builder: (ctx) => AddDialog( - managerId: managerId, - userId: userId, - role: roleId, - title: "Claims", - // policyNumber: 'POC_n1', - // policyNumber: selectedPolicyNumber, - onSubmit: (value) { - debugPrint("New Claims: $value"); - - refresh(); - // enrollKey.currentState?.getEndrosmentList(selectedPolicyNumber); - - // Update claim list or call API - }, - ), - ); - }, - child: Container( - padding: EdgeInsets.all(4.8), - decoration: BoxDecoration( - color: const Color(0xFF2E7D6E), - borderRadius: BorderRadius.circular(8.0), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Tooltip( - message: 'Create Claims', - child: Icon( - Icons.add, - color: Colors.white, - size: 18, - ), - ), - ], - ), - ), - ), - - // SizedBox(width: 10), - // GestureDetector( - // onTap: () { - // // print('Export'); - // }, - // child: Container( - // padding: EdgeInsets.all(8.0), - // decoration: BoxDecoration( - // color: Color(0xFF425B5B), - // borderRadius: BorderRadius.circular(8.0), - // ), - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Icon(Icons.add, color: Colors.white), - // - // if (!ResponsiveLayout.isMobile(context)) ...[ - // SizedBox(width: 10), - // - // GestureDetector( - // onTap: () { - // // context.go(AppRoutes.agent / create); - // context.go('/staff/create'); - // }, - // child: Text( - // 'Create New Staff', - // style: GoogleFonts.inter( - // color: Colors.white, - // fontWeight: FontWeight.w600, - // fontSize: 14, - // ), - // ), - // ), - // ], - // ], - // ), - // ), - // ), - ], - ), - ), - SizedBox(height: 10), - if (!ResponsiveLayout.isMobile(context)) - Container( - decoration: BoxDecoration( - // color: const Color(0xFFEDF6F5), - color: Color(0xFFF1F5F9), - borderRadius: BorderRadius.circular(6), - ), - padding: const EdgeInsets.symmetric( - vertical: 12, - horizontal: 16, - ), - child: Row( - children: [ - Expanded( - flex: 1, - child: Text('S.No.', style: _headerStyle), - ), - Expanded( - flex: 2, - child: Text('Vehicle.No.', style: _headerStyle), - ), - Expanded( - flex: 2, - child: Text('Insurer', style: _headerStyle), - ), - Expanded( - flex: 3, - child: Text('Policy No.', style: _headerStyle), - ), - Expanded( - flex: 2, - child: Text( - 'Policy Expiry Date', - style: _headerStyle, - ), - ), - Expanded( - flex: 3, - child: Text('Claim Type', style: _headerStyle), - ), - Expanded( - flex: 2, - child: Text('Claim No.', style: _headerStyle), - ), - Expanded( - flex: 2, - child: Text('Status', style: _headerStyle), - ), - ], - ), - ), - Expanded( - child: Container( - color: Colors.white, - child: _buildDataTable(context), - ), - ), - ], - ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildHeaderTabs(context), + const SizedBox(height: 14), + _buildFilterBar(context), + const SizedBox(height: 12), + Expanded( + child: isLoading + ? const Center(child: CircularProgressIndicator()) + : ResponsiveLayout.isMobile(context) + ? _buildMobileList() + : _buildDesktopTable(), ), - ), - - Container( - // height: 20, - width: MediaQuery.of(context).size.width, - // color: Colors.green.shade50, - child: PaginationControls( - currentPage: currentPage, - itemsPerPage: itemsPerPage, - // totalItems: dataVal.length, - totalItems: filteredData.length, - // activeColor: layoutColor, // your theme color - onPageChanged: (page) { - setState(() { - currentPage = page; - }); - }, - onItemsPerPageChanged: (items) { - setState(() { - itemsPerPage = items; - currentPage = 1; - }); - }, - ), - ), - ], + const SizedBox(height: 8), + _buildFooter(context), + ], + ), ), - ) ), - ); - } - - Widget _buildDataTable(BuildContext context) { - if (filteredData.isEmpty) { - return const SizedBox( - height: 50, - child: Center(child: Text('No available data')), - ); - } - - final sortedData = [..._paginatedData]; - return ListView.builder( - itemCount: ResponsiveLayout.isMobile(context) - ? sortedData - .length // only cards for mobile - : sortedData.length + 1, // +1 for header in desktop - itemBuilder: (context, index) { - if (!ResponsiveLayout.isMobile(context) && index == 0) { - return _buildHeader(); - } - - final startIndex = (currentPage - 1) * itemsPerPage; - final item = - sortedData[index - (ResponsiveLayout.isMobile(context) ? 0 : 1)]; - final sno = startIndex + index; - - return !ResponsiveLayout.isMobile(context) - ? Container(color: Colors.white, child: _buildDataRow(item, sno)) - : _buildDataCard(item, sno); - }, - ); - } - - Widget _buildHeader() { - return SizedBox.shrink(); - } - - Widget _buildDataRow(Map item, sno) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), - // margin: const EdgeInsets.only(top: 10), - decoration: BoxDecoration( - color: Colors.white, - // color: Color(0xFFE0F7F9), - border: const Border( - bottom: BorderSide(color: Color(0xFFEAEAEA), width: 1), - ), - borderRadius: BorderRadius.circular(8), ), - child: Row( - children: [ - Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)), - Expanded( - flex: 2, - child: Text( - item['reg_no'] ?? '-', // fallback - style: _dataBold, - ), - ), - Expanded( - flex: 2, - child: Text( - item['insurer_short_name'] ?? '-', // fallback - style: _dataBold, - ), - ), - Expanded( - flex: 3, - child: Text(item['policy_no'] ?? '-', style: _dataBold), - ), - Expanded( - flex: 2, - child: Text( - // _formatDate(item['policy_end_date']) ?? '-', - item['policy_end_date'] != null - ? _formatDate(item['policy_end_date']) - : '-', - style: _dataBold, - ), - ), - Expanded( - flex: 3, - child: Text( - item['claim_type_value'] ?? '-', // fallback - style: _dataBold, - ), - ), - Expanded( - flex: 2, - child: Text( - item['claim_number'] ?? '-', // fallback - style: _dataBold, - ), - ), - Expanded( - flex: 2, - child: Text( - item['claim_status_value'] ?? '-', // fallback - style: _dataBold, - ), - ), + ); + } - // Expanded( - // flex: 1, - // child: Row( - // children: [ - // PopupMenuButton( - // color: Colors.white, - // padding: EdgeInsets.zero, - // offset: Offset(0, 30), - // icon: Icon( - // Icons.more_vert, - // color: Color(0xFF475569), - // size: 14, - // ), - // itemBuilder: (context) => [ - // CustomPopupMenuEntry( - // child: Container( - // padding: EdgeInsets.symmetric( - // horizontal: 8, - // vertical: 8, - // ), - // child: Column( - // mainAxisSize: MainAxisSize.min, - // mainAxisAlignment: MainAxisAlignment.center, - // children: _buildPopupMenuActions(context, item), - // ), - // ), - // ), - // ], - // ), - // ], - // ), - // ), + Widget _buildHeaderTabs(BuildContext context) { + return Text( + 'Claims', + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ); + } + + // Widget _tabSwitcher() { + // return Container( + // padding: const EdgeInsets.all(4), + // decoration: BoxDecoration( + // color: const Color(0xFFF1F5F9), + // borderRadius: BorderRadius.circular(10), + // ), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // _tabChip( + // label: 'Pending Claims', + // count: _pendingCount, + // selected: _pendingTab, + // onTap: () => _setTab(true), + // ), + // const SizedBox(width: 6), + // _tabChip( + // label: 'Settled Claims', + // count: _settledCount, + // selected: !_pendingTab, + // onTap: () => _setTab(false), + // ), + // ], + // ), + // ); + // } + + // Widget _tabChip({ + // required String label, + // required int count, + // required bool selected, + // required VoidCallback onTap, + // }) { + // return Material( + // color: Colors.transparent, + // child: InkWell( + // onTap: onTap, + // borderRadius: BorderRadius.circular(8), + // child: AnimatedContainer( + // duration: const Duration(milliseconds: 180), + // padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + // decoration: BoxDecoration( + // color: selected ? Colors.white : Colors.transparent, + // borderRadius: BorderRadius.circular(8), + // boxShadow: selected + // ? [ + // BoxShadow( + // color: Colors.black.withValues(alpha: 0.06), + // blurRadius: 8, + // offset: const Offset(0, 2), + // ), + // ] + // : null, + // ), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Text( + // label, + // style: GoogleFonts.inter( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: selected ? _teal : _muted, + // ), + // ), + // const SizedBox(width: 8), + // Container( + // width: 26, + // height: 26, + // alignment: Alignment.center, + // decoration: BoxDecoration( + // color: selected + // ? _teal.withValues(alpha: 0.12) + // : const Color(0xFFE2E8F0), + // shape: BoxShape.circle, + // ), + // child: Text( + // '$count', + // style: GoogleFonts.inter( + // fontSize: 11, + // fontWeight: FontWeight.w700, + // color: selected ? _teal : _muted, + // ), + // ), + // ), + // ], + // ), + // ), + // ), + // ); + // } + + Widget _buildFilterBar(BuildContext context) { + final isMobile = ResponsiveLayout.isMobile(context); + final inner = isMobile + ? Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _filterControlsRow(context), + const SizedBox(height: 10), + _buildSearchAndActions(context), + ], + ) + : Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _filterControlsRow(context)), + _buildSearchAndActions(context), + ], + ); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + decoration: BoxDecoration( + border: Border.all(color: _border), + borderRadius: BorderRadius.circular(10), + color: Colors.white, + ), + child: inner, + ); + } + + Widget _filterControlsRow(BuildContext context) { + final isMobile = ResponsiveLayout.isMobile(context); + final claimTypeChoices = _claimTypeFilterChoices( + _uniqueFrom((r) => _pickStr(r, ['claim_type_value'])), + ); + final insurerChoices = _insurerFilterChoices( + _uniqueFrom((r) => _pickStr(r, ['insurer_short_name'])), + ); + final statusChoices = _statusFilterChoices( + _uniqueFrom((r) => _pickStr(r, ['claim_status_value'])), + ); + + final dateW = isMobile ? 168.0 : 175.0; + final dropW = isMobile ? 128.0 : 140.0; + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DateRangePickerField( + startController: _startDateController, + endController: _endDateController, + hintText: 'Select date range', + showLabelAboveField: false, + txtwidth: dateW, + txtheight: 38, + lastDate: DateTime.now(), + useFormField: false, + ), + const SizedBox(width: 8), + _buildFilterDropdownSearch( + width: dropW, + selectedValue: _filterClaimType, + choices: claimTypeChoices, + twoLineItems: true, + onChanged: (v) => setState(() => _filterClaimType = v), + ), + const SizedBox(width: 8), + _buildFilterDropdownSearch( + width: dropW + 8, + selectedValue: _filterInsurer, + choices: insurerChoices, + twoLineItems: true, + onChanged: (v) => setState(() => _filterInsurer = v), + ), + const SizedBox(width: 8), + _buildFilterDropdownSearch( + width: dropW, + selectedValue: _filterStatus, + choices: statusChoices, + onChanged: (v) => setState(() => _filterStatus = v), + ), + const SizedBox(width: 8), + _squareFilterButton( + icon: Icons.search, + filled: true, + onTap: _applyFilterBar, + ), + const SizedBox(width: 8), + _squareFilterButton( + icon: Icons.refresh, + filled: false, + onTap: _clearFilters, + ), ], ), ); } - Widget _buildDataCard(Map item, int sno) { + Widget _squareFilterButton({ + required VoidCallback onTap, + required bool filled, + IconData? icon, + String? assetPath, + String? tooltip, + }) { + assert(icon != null || assetPath != null); + + final child = assetPath != null + ? Image.asset( + assetPath, + height: 16, + width: 16, + color: filled ? Colors.white : _muted, + errorBuilder: (_, __, ___) => Icon( + Icons.open_in_new_rounded, + size: 20, + color: filled ? Colors.white : _muted, + ), + ) + : Icon( + icon, + size: 20, + color: filled ? Colors.white : _muted, + ); + + final button = Material( + color: filled ? _teal : Colors.white, + borderRadius: BorderRadius.circular(8), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + width: 38, + height: 38, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: filled ? null : Border.all(color: _border), + ), + child: child, + ), + ), + ); + + if (tooltip != null) { + return Tooltip(message: tooltip, child: button); + } + return button; + } + + Widget _buildSearchAndActions(BuildContext context) { + final isMobile = ResponsiveLayout.isMobile(context); + final searchField = ThemedSearchField( + hintText: 'Search...', + backgroundColor: Colors.white, + txtHeight: 38, + onChanged: _onQuickSearch, + controller: _quickSearchController, + txtwidth: isMobile + ? MediaQuery.sizeOf(context).width * 0.5 + : 200, + ); + + final actions = Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 10), + _squareFilterButton( + icon: Icons.add, + filled: true, + tooltip: 'Add claim', + onTap: () { + showDialog( + context: context, + builder: (ctx) => AddClaimStepperDialog( + managerId: managerId, + userId: userId, + role: roleId, + onSuccess: refresh, + ), + ); + }, + ), + const SizedBox(width: 8), + _squareFilterButton( + filled: true, + assetPath: 'assets/miscellaneous/export.png', + tooltip: 'Export', + onTap: _onExportClaims, + ), + ], + ); + + if (isMobile) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: searchField), + actions, + ], + ); + } + + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + searchField, + actions, + ], + ); + } + + Widget _buildFooter(BuildContext context) { + final total = filteredData.length; + final showing = _paginatedSorted.length; + final label = total == 1 ? 'claim' : 'claims'; + + return Row( + children: [ + Text( + 'Showing $showing of $total $label', + style: GoogleFonts.inter(fontSize: 12, color: _muted), + ), + const Spacer(), + Text( + total == 0 ? 'Page 1' : 'Page $currentPage', + style: GoogleFonts.inter(fontSize: 12, color: _muted), + ), + const SizedBox(width: 16), + PaginationControls( + currentPage: currentPage, + itemsPerPage: itemsPerPage, + totalItems: filteredData.length, + onPageChanged: (page) { + setState(() => currentPage = page); + }, + onItemsPerPageChanged: (items) { + setState(() { + itemsPerPage = items; + currentPage = 1; + }); + }, + ), + ], + ); + } + + int _calculatePendingDays(String? createdAt) { + if (createdAt == null || createdAt.isEmpty) return 0; + try { + final created = DateFormat('dd-MM-yyyy').parse(createdAt.split(' ')[0]); + final today = DateTime.now(); + return today.difference(created).inDays; + } catch (e) { + return 0; + } + } + + bool _isClosedClaimStatus(Map item) { + final status = + _pickStr(item, ['claim_status_value', 'status']).toLowerCase(); + return status.contains('closed') || status.contains('settled'); + } + + String _pendingDaysLabel(Map item) { + final days = _calculatePendingDays(item['created_at']?.toString()); + return _isClosedClaimStatus(item) ? '-' : '$days'; + } + + Widget _pendingDaysCell(Map item) { + return Expanded( + flex: 1, + child: Builder( + builder: (context) { + final days = _calculatePendingDays(item['created_at']?.toString()); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 12), + child: Center( + child: Text( + _isClosedClaimStatus(item) ? '-' : '$days', + textAlign: TextAlign.center, + style: _dataBold.copyWith( + color: _isClosedClaimStatus(item) + ? _muted + : (days > 7 + ? Colors.red + : days > 3 + ? Colors.orange + : Colors.green), + ), + ), + ), + ); + }, + ), + ); + } + + Widget _buildDesktopTable() { + if (filteredData.isEmpty) { + return Center( + child: Text( + 'No claims match your filters.', + style: GoogleFonts.inter(color: _muted), + ), + ); + } + + final rows = _paginatedSorted; + final startSerial = (currentPage - 1) * itemsPerPage; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _tableHeaderRow(), + Expanded( + child: Scrollbar( + controller: _tableVScrollController, + thumbVisibility: true, + interactive: true, + child: ListView( + controller: _tableVScrollController, + primary: false, + padding: EdgeInsets.zero, + physics: const ClampingScrollPhysics(), + children: [ + for (var index = 0; index < rows.length; index++) ...[ + if (index > 0) const Divider(height: 1, color: _border), + _pendingDataRow(rows[index], startSerial + index + 1), + ], + ], + ), + ), + ), + ], + ); + } + + Widget _headerCell( + String text, { + int flex = 1, + TextAlign align = TextAlign.start, + }) { + return Expanded( + flex: flex, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), + child: Align( + alignment: align == TextAlign.center + ? Alignment.center + : Alignment.centerLeft, + child: Text( + text.toUpperCase(), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: align, + style: GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w700, + letterSpacing: 0.3, + color: const Color(0xFF475569), + ), + ), + ), + ), + ); + } + + Widget _headerCellFixed( + String text, { + required double width, + TextAlign align = TextAlign.start, + }) { + return SizedBox( + width: width, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), + child: Align( + alignment: align == TextAlign.center + ? Alignment.center + : Alignment.centerLeft, + child: Text( + text.toUpperCase(), + softWrap: false, + maxLines: 1, + overflow: TextOverflow.visible, + textAlign: align, + style: GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w700, + letterSpacing: 0.3, + color: const Color(0xFF475569), + ), + ), + ), + ), + ); + } + + Widget _headerCellSno() { + return Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.only(left: 6, right: 2, top: 12, bottom: 12), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + 'S NO', + softWrap: true, + maxLines: 2, + style: GoogleFonts.inter( + fontSize: 9, + fontWeight: FontWeight.w700, + letterSpacing: 0.2, + color: const Color(0xFF475569), + ), + ), + ), + ), + ); + } + + Widget _tableHeaderRow() { return Container( - margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), - padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFFF6FEFD), - borderRadius: BorderRadius.circular(8.0), - border: Border.all(color: const Color(0xffD9EBE8)), + color: _surfaceGrey, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + _headerCellSno(), + _headerCell('Created Date', flex: 2), + _headerCell('Claim Type', flex: 2), + _headerCell('Vehicle No', flex: 2), + _headerCell('Insured Name', flex: 2), + _headerCell('Policy Number', flex: 2), + _headerCell('Claim Number', flex: 2), + _headerCell('Accident Date', flex: 2), + _headerCell('Insurer', flex: 2), + _headerCell('Broker', flex: 2), + _headerCell('Partner', flex: 2), + _headerCell('Remarks', flex: 2), + _headerCell('Status', flex: 2), + _headerCell('Pending Days', flex: 1, align: TextAlign.center), + _headerCell('Action', flex: 2, align: TextAlign.center), + ], + ), + ); + } + + Widget _cell(Widget child, {int flex = 1}) { + return Expanded( + flex: flex, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), + child: Align( + alignment: Alignment.centerLeft, + child: child, + ), + ), + ); + } + + Widget _cellFixed(Widget child, {required double width}) { + return SizedBox( + width: width, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), + child: Align( + alignment: Alignment.centerLeft, + child: child, + ), + ), + ); + } + + Widget _cellSno(Widget child) { + return Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.only(left: 6, right: 2, top: 12, bottom: 12), + child: Align( + alignment: Alignment.centerLeft, + child: child, + ), + ), + ); + } + + Widget _cellText( + String text, { + int flex = 1, + int maxLines = 2, + FontWeight? fontWeight, + bool ellipsis = true, + }) { + return _cell( + Text( + text, + style: _dataStyle.copyWith( + fontWeight: fontWeight ?? FontWeight.w400, + ), + maxLines: maxLines, + softWrap: maxLines > 1, + overflow: ellipsis ? TextOverflow.ellipsis : TextOverflow.visible, + ), + flex: flex, + ); + } + + Widget _cellTextFixed( + String text, { + required double width, + int maxLines = 1, + FontWeight? fontWeight, + TextAlign align = TextAlign.start, + }) { + return SizedBox( + width: width, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), + child: Align( + alignment: align == TextAlign.center + ? Alignment.center + : Alignment.centerLeft, + child: Text( + text, + textAlign: align, + style: _dataStyle.copyWith( + fontWeight: fontWeight ?? FontWeight.w400, + ), + maxLines: maxLines, + softWrap: maxLines > 1, + overflow: TextOverflow.visible, + ), + ), + ), + ); + } + + Widget _cellRemarks(String text, {int flex = 3}) { + final display = text.trim().isEmpty ? '-' : text.trim(); + final textWidget = Text( + display, + style: _dataStyle, + maxLines: 1, + softWrap: false, + overflow: TextOverflow.ellipsis, + ); + if (display == '-') { + return _cell(textWidget, flex: flex); + } + return _cell( + Tooltip( + richMessage: WidgetSpan( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 420), + child: Text( + display, + style: GoogleFonts.inter( + fontSize: 13, + height: 1.45, + fontWeight: FontWeight.w400, + color: Colors.white, + ), + ), + ), + ), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + margin: const EdgeInsets.only(bottom: 6), + preferBelow: false, + verticalOffset: 10, + waitDuration: const Duration(milliseconds: 250), + showDuration: const Duration(seconds: 10), + decoration: BoxDecoration( + color: const Color(0xFF334155), + borderRadius: BorderRadius.circular(8), + boxShadow: const [ + BoxShadow( + color: Color(0x40000000), + blurRadius: 10, + offset: Offset(0, 3), + ), + ], + ), + child: textWidget, + ), + flex: flex, + ); + } + + Widget _pendingDataRow(Map item, int sno) { + final createdRaw = _pickStr(item, [ + 'created_at', + 'created_date', + 'created_on', + ]); + final accidentRaw = _pickStr(item, [ + 'date_of_incident', + 'accident_date', + 'accident_datetime', + 'date_of_accident', + ]); + final insured = _pickStr(item, [ + 'insured_name', + 'customer_name', + 'proposer_name', + ]); + final remarks = _pickStr(item, ['remark']); + + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _cellSno(Text('$sno', style: _dataStyle)), + _cellText( + createdRaw != '-' ? _formatDate(createdRaw) : '-', + flex: 2, + maxLines: 2, + ), + _cell( + _claimTypeChip(_pickStr(item, ['claim_type_value'])), + flex: 2, + ), + _cellText(_pickStr(item, ['reg_no']), flex: 2, maxLines: 2), + _cellText(insured, flex: 2, maxLines: 2, fontWeight: FontWeight.w600), + _cellText( + _pickStr(item, ['policy_no', 'policy_number']), + flex: 2, + maxLines: 2, + ), + _cellText( + _pickStr(item, ['claim_number']), + flex: 2, + maxLines: 2, + ), + _cellText( + accidentRaw != '-' ? _formatDate(accidentRaw) : '-', + flex: 2, + maxLines: 2, + ), + _cellText(_pickStr(item, ['insurer_short_name']), flex: 2, maxLines: 2), + _cellText(_pickStr(item, ['broker_name', 'broker']), flex: 2, maxLines: 2), + _cellText( + _pickStr(item, ['agent_name', 'partner_name', 'partner']), + flex: 2, + maxLines: 2, + ), + _cellRemarks(remarks, flex: 2), + _cell( + _statusChip(_pickStr(item, ['claim_status_value'])), + flex: 2, + ), + _pendingDaysCell(item), + _cell(Center(child: _actionIcons(item)), flex: 2), + ], + ); + } + + // Widget _settledDataRow(Map item, int sno) { + // final accidentRaw = _pickStr(item, [ + // 'date_of_incident', + // 'accident_date', + // 'accident_datetime', + // 'date_of_accident', + // ]); + // final insured = _pickStr(item, [ + // 'insured_name', + // 'customer_name', + // 'proposer_name', + // ]); + // final remarks = _pickStr(item, ['remarks', 'claim_description']); + // final settled = _pickStr(item, [ + // 'settled_amount', + // 'claim_amount', + // 'settlement_amount', + // ]); + // return Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // _cellSno(Text('$sno', style: _dataStyle)), + // _cellText(_pickStr(item, ['reg_no']), flex: 2, maxLines: 2), + // _cellText(insured, flex: 2, maxLines: 2, fontWeight: FontWeight.w600), + // _cellText(_pickStr(item, ['policy_no']), flex: 2, maxLines: 2), + // _cellText( + // accidentRaw != '-' ? _formatDate(accidentRaw) : '-', + // flex: 2, + // maxLines: 2, + // ), + // _cellText(_pickStr(item, ['insurer_short_name']), flex: 2, maxLines: 2), + // _cellText(_pickStr(item, ['broker_name', 'broker']), flex: 2, maxLines: 2), + // _cellText( + // _pickStr(item, ['agent_name', 'partner_name', 'partner']), + // flex: 2, + // maxLines: 2, + // ), + // _cellRemarks(remarks, flex: 2), + // _cell( + // _statusChip(_pickStr(item, ['claim_status_value'])), + // flex: 2, + // ), + // _pendingDaysCell(item), + // _cellText(settled == '-' ? '-' : settled, flex: 2, maxLines: 2), + // _cell(_actionIcons(item), flex: 1), + // ], + // ); + // } + + Widget _actionIcons(Map item) { + return Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 28, minHeight: 28), + tooltip: 'Edit', + icon: const Icon(Icons.edit_outlined, color: _teal, size: 18), + onPressed: () { + showDialog( + context: context, + builder: (ctx) => AddClaimStepperDialog( + managerId: managerId, + userId: userId, + role: roleId, + claimToEdit: item, + onSuccess: refresh, + ), + ); + }, + ), + if (_canShowDeleteAction) const SizedBox(width: 6), + // IconButton( + // padding: EdgeInsets.zero, + // constraints: const BoxConstraints(minWidth: 30, minHeight: 30), + // tooltip: 'Upload', + // icon: const Icon(Icons.cloud_upload_outlined, color: _teal, size: 18), + // onPressed: () {}, + // ), + // IconButton( + // padding: EdgeInsets.zero, + // constraints: const BoxConstraints(minWidth: 30, minHeight: 30), + // tooltip: 'Download', + // icon: const Icon( + // Icons.cloud_download_outlined, + // color: _actionDownloadBlue, + // size: 18, + // ), + // onPressed: () {}, + // ), + if (_canShowDeleteAction) + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 28, minHeight: 28), + tooltip: 'Delete', + icon: const Icon( + Icons.delete_outline, + color: Color(0xFFC62828), + size: 18, + ), + onPressed: () { + final claimId = _pickStr(item, ['id']); + _confirmDelete(context, claimId); + }, + ), + ], + ); + } + + Widget _claimTypeChip(String text) { + final t = text.toLowerCase(); + Color bg; + Color fg; + if (t.contains('theft')) { + bg = const Color(0xFFFFEBEE); + fg = const Color(0xFFC62828); + } else if (t.contains('tp') && !t.contains('own')) { + bg = const Color(0xFFE3F2FD); + fg = const Color(0xFF1565C0); + } else if (t.contains('od') || t.contains('own damage')) { + bg = const Color(0xFFF3E5F5); + fg = const Color(0xFF6A1B9A); + } else { + bg = const Color(0xFFF3E5F5); + fg = const Color(0xFF6A1B9A); + } + return Align( + alignment: Alignment.centerLeft, + child: _pillWrapWords(text, bg, fg), + ); + } + + String _statusDisplayLabel(String text) { + final t = text.trim(); + if (t.isEmpty || t == '-') return t.isEmpty ? '-' : t; + final compact = t.replaceAll(RegExp(r'\s+'), '').toUpperCase(); + if (compact == 'INPROGRESS') return 'IN PROGRESS'; + return t; + } + + Widget _statusChip(String text) { + final label = _statusDisplayLabel(text); + final t = label.toLowerCase(); + Color bg; + Color fg; + if (t.contains('reject')) { + bg = const Color(0xFFFFEBEE); + fg = const Color(0xFFC62828); + } else if (t.contains('open')) { + bg = const Color(0xFFFFF3E0); + fg = const Color(0xFFEF6C00); + } else if (t.contains('progress')) { + bg = const Color(0xFFF3E5F5); + fg = const Color(0xFF6A1B9A); + } else if (t.contains('settled')) { + bg = const Color(0xFFE8F5E9); + fg = const Color(0xFF2E7D32); + } else { + bg = const Color(0xFFF1F5F9); + fg = const Color(0xFF475569); + } + return Align( + alignment: Alignment.centerLeft, + child: _pill(label, bg, fg), + ); + } + + Widget _pill(String text, Color bg, Color fg, {bool showFull = false}) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + text, + maxLines: 1, + softWrap: false, + overflow: TextOverflow.visible, + style: GoogleFonts.inter( + fontSize: 10.5, + fontWeight: FontWeight.w600, + color: fg, + ), + ), + ); + } + + /// Pill label that wraps only between words, never inside a word. + Widget _pillWrapWords(String text, Color bg, Color fg) { + final style = GoogleFonts.inter( + fontSize: 10.5, + fontWeight: FontWeight.w600, + color: fg, + ); + final words = text.trim().split(RegExp(r'\s+')).where((w) => w.isNotEmpty); + if (words.isEmpty) { + return _pill('-', bg, fg); + } + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(20), + ), + child: Wrap( + spacing: 4, + runSpacing: 2, + crossAxisAlignment: WrapCrossAlignment.center, + children: words + .map((w) => Text(w, style: style)) + .toList(), + ), + ); + } + + TextStyle get _dataStyle => GoogleFonts.inter( + fontSize: 11.5, + fontWeight: FontWeight.w400, + color: const Color(0xFF0F172A), + ); + + TextStyle get _dataBold => GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w700, + color: const Color(0xFF0F172A), + ); + + Widget _buildMobileList() { + if (filteredData.isEmpty) { + return Center( + child: Text( + 'No claims match your filters.', + style: GoogleFonts.inter(color: _muted), + ), + ); + } + final rows = _paginatedSorted; + final startSerial = (currentPage - 1) * itemsPerPage; + + return ListView.separated( + itemCount: rows.length, + separatorBuilder: (_, _) => const SizedBox(height: 10), + itemBuilder: (context, index) { + final item = rows[index]; + final sno = startSerial + index + 1; + return _mobileCard(item, sno); + }, + ); + } + + Widget _mobileCard(Map item, int sno) { + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: _border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - //reg no - Text( - item['reg_no'] != null ? _formatDate(item['reg_no']) : '-', - style: _headerStyle, - ), - - // PopupMenuButton( - // color: Colors.white, - // padding: EdgeInsets.zero, - // offset: Offset(0, 30), - // icon: Icon(Icons.more_vert, color: Color(0xFF475569), size: 14), - // itemBuilder: (context) => [ - // CustomPopupMenuEntry( - // child: Container( - // padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), - // child: Column( - // mainAxisSize: MainAxisSize.min, - // mainAxisAlignment: MainAxisAlignment.center, - // children: _buildPopupMenuActions(context, item), - // ), - // ), - // ), - // ], - // ), + Text('#$sno', style: _dataStyle.copyWith(color: _muted)), + const Spacer(), + _statusChip(_pickStr(item, ['claim_status_value'])), ], ), - const Divider(color: Color(0xffD9EBE8), thickness: 0.8), - - // Company + Vehicle Reg No - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, + const SizedBox(height: 8), + Text( + _pickStr(item, ['reg_no']), + style: _dataStyle.copyWith( + fontWeight: FontWeight.w700, + fontSize: 14, + ), + ), + const SizedBox(height: 4), + Text( + _pickStr(item, [ + 'insured_name', + 'customer_name', + 'proposer_name', + ]), + style: _dataStyle.copyWith(fontWeight: FontWeight.w500), + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - - mainAxisAlignment: MainAxisAlignment.start, - children: [ - //company - Text("Insurer", style: _cardheaderStyle), - Text( - item['policy_end_date'] != null - ? _formatDate(item['policy_end_date']) - : '-', - style: _cardBodyStyle, - maxLines: 3, - softWrap: true, - ), - ], - ), - ), - SizedBox(width: 5), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - //status - Text("Claim Type", style: _cardheaderStyle), - Text( - item['claim_type_value'] ?? '-', - style: _cardBodyStyle, - maxLines: 2, - softWrap: true, - ), - ], - ), - ), + _claimTypeChip(_pickStr(item, ['claim_type_value'])), ], ), - const SizedBox(height: 15), - - // Created On + Remarks - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - //date and time - Text("policy Number", style: _cardheaderStyle), - Text(item['policy_no'] ?? '-', style: _cardBodyStyle), - ], - ), - ), - SizedBox(width: 5), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - //remarks - Text("Claim Number", style: _cardheaderStyle), - Text( - item['claim_number'] ?? '-', - style: _cardBodyStyle, - maxLines: 3, - softWrap: true, - ), - ], - ), - ), - ], + const Divider(height: 20), + _mobilePair( + 'Policy', + _pickStr(item, ['policy_no', 'policy_number']), ), - const SizedBox(height: 15), - - // Created On + Remarks - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - //date and time - Text("Status", style: _cardheaderStyle), - Text( - item['claim_status_value'] ?? '-', - style: _cardBodyStyle, - ), - ], - ), - ), - ], + _mobilePair( + 'Claim No', + _pickStr(item, ['claim_number']), + ), + _mobilePair('Insurer', _pickStr(item, ['insurer_short_name'])), + _mobilePair('Pending days', _pendingDaysLabel(item)), + // if (!_pendingTab) ...[ + // _mobilePair( + // 'Settled (₹)', + // _pickStr(item, [ + // 'settled_amount', + // 'claim_amount', + // 'settlement_amount', + // ]), + // ), + // ], + const SizedBox(height: 8), + Align( + alignment: Alignment.centerRight, + child: _actionIcons(item), ), ], ), ); } - static final _dataBold = GoogleFonts.inter( - fontSize: 11.5, - fontWeight: FontWeight.w400, - color: Color(0xFF000000), - ); - - static final _dataSub = GoogleFonts.inter( - fontSize: 10, - fontWeight: FontWeight.w300, - color: Color(0xFF585757), - ); - - static final _headerStyle = GoogleFonts.poppins( - fontSize: 11.2, - fontWeight: FontWeight.w500, - color: Color(0xFF1E293B), - ); - - static final _cardheaderStyle = GoogleFonts.inter( - color: Colors.black, - fontWeight: FontWeight.w600, - fontSize: 12, - ); - - static final _cardBodyStyle = GoogleFonts.inter( - color: const Color(0xFF545454), - fontWeight: FontWeight.w400, - fontSize: 12, - ); + Widget _mobilePair(String k, String v) { + return Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 88, + child: Text( + k, + style: GoogleFonts.inter( + fontSize: 11, + color: _muted, + fontWeight: FontWeight.w500, + ), + ), + ), + Expanded( + child: Text(v, style: _dataStyle), + ), + ], + ), + ); + } } diff --git a/lib/presentation/screens/Enquiry/policy_claims_endros/create_endorsement_dialog.dart b/lib/presentation/screens/Enquiry/policy_claims_endros/create_endorsement_dialog.dart index 1d4a663..bc23c71 100644 --- a/lib/presentation/screens/Enquiry/policy_claims_endros/create_endorsement_dialog.dart +++ b/lib/presentation/screens/Enquiry/policy_claims_endros/create_endorsement_dialog.dart @@ -19,6 +19,7 @@ import '../../../themes/indicators/customizd_file_upload.dart'; import '../../../themes/indicators/input_field_decoration.dart'; import '../../../themes/indicators/search_field_theme.dart'; import '../../../themes/indicators/text_field_theme.dart'; +import '../../../widgets/policy_search_results_list.dart'; class CreateEndorsementDialog extends ConsumerStatefulWidget { final String title; @@ -881,66 +882,27 @@ class _CreateEndorsementDialogState }, ), - /// 🔹 Vehicle Results Dropdown if (filteredVehicleData.isNotEmpty) - Container( - margin: const EdgeInsets.only(top: 6), - height: 150, - decoration: BoxDecoration( - color: Colors.white, - border: Border.all( - color: const Color(0xFFE5E7EB), - ), - borderRadius: BorderRadius.circular( - 12, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity( - 0.05, - ), - blurRadius: 6, - ), - ], - ), - child: ListView.builder( - padding: EdgeInsets.zero, - itemCount: - filteredVehicleData.length, - itemBuilder: (context, index) { - final vehicle = - filteredVehicleData[index]; - return InkWell( - onTap: () { - setState(() { - controllers['policyNum'] - ?.text = - vehicle['policy_number'] ?? - ''; - _vehicleSearchController - .text = - vehicle['policy_number'] ?? - ''; - filteredVehicleData.clear(); - }); - }, - child: Padding( - padding: - const EdgeInsets.symmetric( - vertical: 8, - horizontal: 12, - ), - child: Text( - vehicle['policy_number'] ?? - '-', - style: _textStyle1, - overflow: - TextOverflow.ellipsis, - ), - ), - ); - }, - ), + PolicySearchResultsList( + rows: filteredVehicleData, + fromVehicle: true, + borderRadius: 12, + onSelected: (vehicle) { + setState(() { + controllers['policyNum']?.text = + PolicySearchUtils + .policyNumberFromRow( + vehicle, + ); + _vehicleSearchController.text = + PolicySearchUtils + .vehicleInputValueOnSelect( + vehicle, + ); + filteredVehicleData.clear(); + }); + validateSteps(); + }, ), ], ), @@ -969,66 +931,26 @@ class _CreateEndorsementDialogState }, ), - /// 🔹 Policy Results Dropdown if (filteredPolicyData.isNotEmpty) - Container( - margin: const EdgeInsets.only(top: 6), - height: 150, - decoration: BoxDecoration( - color: Colors.white, - border: Border.all( - color: const Color(0xFFE5E7EB), - ), - borderRadius: BorderRadius.circular( - 12, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity( - 0.05, - ), - blurRadius: 6, - ), - ], - ), - child: ListView.builder( - padding: EdgeInsets.zero, - itemCount: - filteredPolicyData.length, - itemBuilder: (context, index) { - final policy = - filteredPolicyData[index]; - return InkWell( - onTap: () { - setState(() { - controllers['policyNum'] - ?.text = - policy['policy_number'] ?? - ''; - _policySearchController - .text = - policy['policy_number'] ?? - ''; - filteredPolicyData.clear(); - }); - }, - child: Padding( - padding: - const EdgeInsets.symmetric( - vertical: 8, - horizontal: 12, - ), - child: Text( - policy['policy_number'] ?? - '-', - style: _textStyle1, - overflow: - TextOverflow.ellipsis, - ), - ), + PolicySearchResultsList( + rows: filteredPolicyData, + fromVehicle: false, + borderRadius: 12, + onSelected: (policy) { + setState(() { + final policyNo = + PolicySearchUtils + .policyNumberFromRow( + policy, ); - }, - ), + controllers['policyNum']?.text = + policyNo; + _policySearchController.text = + policyNo; + filteredPolicyData.clear(); + }); + validateSteps(); + }, ), ], ), diff --git a/lib/presentation/screens/Enquiry/policy_claims_endros/endorsomentUpdateValidation.dart b/lib/presentation/screens/Enquiry/policy_claims_endros/endorsomentUpdateValidation.dart index f6cfe9e..2a4dca6 100644 --- a/lib/presentation/screens/Enquiry/policy_claims_endros/endorsomentUpdateValidation.dart +++ b/lib/presentation/screens/Enquiry/policy_claims_endros/endorsomentUpdateValidation.dart @@ -30,6 +30,7 @@ import 'package:flutter/services.dart'; import '../../../themes/indicators/search_field_theme.dart'; import '../../../themes/indicators/text_field_theme.dart'; +import '../../../widgets/policy_search_results_list.dart'; final digitsOnlyFormatter = [FilteringTextInputFormatter.digitsOnly]; @@ -1083,84 +1084,32 @@ class _endorsomentValidationState extends ConsumerState { }, ), - /// 🔹 Vehicle Results Dropdown if (filteredVehicleData .isNotEmpty) - Container( - margin: - const EdgeInsets.only( - top: 6, - ), - height: 150, - decoration: BoxDecoration( - color: - Colors.white, - border: Border.all( - color: - const Color( - 0xFFE5E7EB, - ), - ), - borderRadius: - BorderRadius.circular( - 12, - ), - boxShadow: [ - BoxShadow( - color: Colors - .black - .withOpacity( - 0.05, - ), - blurRadius: 6, - ), - ], - ), - child: ListView.builder( - padding: - EdgeInsets - .zero, - itemCount: - filteredVehicleData - .length, - itemBuilder: (context, index) { - final vehicle = - filteredVehicleData[index]; - return InkWell( - onTap: () { - setState(() { - controllers['policyNum'] - ?.text = - vehicle['policy_number'] ?? - ''; - _vehicleSearchController - .text = - vehicle['policy_number'] ?? - ''; - filteredVehicleData - .clear(); - }); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: - 8, - horizontal: - 12, - ), - child: Text( - vehicle['policy_number'] ?? - '-', - style: - _textStyle1, - overflow: - TextOverflow - .ellipsis, - ), - ), + PolicySearchResultsList( + rows: + filteredVehicleData, + fromVehicle: true, + borderRadius: 12, + onSelected: + (vehicle) { + setState(() { + controllers[ + 'policyNum'] + ?.text = PolicySearchUtils + .policyNumberFromRow( + vehicle, ); - }, - ), + _vehicleSearchController + .text = + PolicySearchUtils + .vehicleInputValueOnSelect( + vehicle, + ); + filteredVehicleData + .clear(); + }); + }, ), ], ), @@ -1196,84 +1145,32 @@ class _endorsomentValidationState extends ConsumerState { }, ), - /// 🔹 Policy Results Dropdown if (filteredPolicyData .isNotEmpty) - Container( - margin: - const EdgeInsets.only( - top: 6, - ), - height: 150, - decoration: BoxDecoration( - color: - Colors.white, - border: Border.all( - color: - const Color( - 0xFFE5E7EB, - ), - ), - borderRadius: - BorderRadius.circular( - 12, - ), - boxShadow: [ - BoxShadow( - color: Colors - .black - .withOpacity( - 0.05, - ), - blurRadius: 6, - ), - ], - ), - child: ListView.builder( - padding: - EdgeInsets - .zero, - itemCount: - filteredPolicyData - .length, - itemBuilder: (context, index) { - final policy = - filteredPolicyData[index]; - return InkWell( - onTap: () { - setState(() { - controllers['policyNum'] - ?.text = - policy['policy_number'] ?? - ''; - _policySearchController - .text = - policy['policy_number'] ?? - ''; - filteredPolicyData - .clear(); - }); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: - 8, - horizontal: - 12, - ), - child: Text( - policy['policy_number'] ?? - '-', - style: - _textStyle1, - overflow: - TextOverflow - .ellipsis, - ), - ), + PolicySearchResultsList( + rows: + filteredPolicyData, + fromVehicle: false, + borderRadius: 12, + onSelected: + (policy) { + setState(() { + final policyNo = + PolicySearchUtils + .policyNumberFromRow( + policy, ); - }, - ), + controllers[ + 'policyNum'] + ?.text = + policyNo; + _policySearchController + .text = + policyNo; + filteredPolicyData + .clear(); + }); + }, ), ], ), diff --git a/lib/presentation/widgets/policy_search_results_list.dart b/lib/presentation/widgets/policy_search_results_list.dart new file mode 100644 index 0000000..d12e814 --- /dev/null +++ b/lib/presentation/widgets/policy_search_results_list.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +/// Shared helpers for vehicle / policy search API dropdown results. +class PolicySearchUtils { + PolicySearchUtils._(); + + static List> normalizeData(dynamic data) { + if (data == null) return []; + if (data is List) { + final out = >[]; + for (final item in data) { + if (item is Map) { + out.add(Map.from(item)); + } + } + return out; + } + if (data is Map) { + return [Map.from(data)]; + } + return []; + } + + static String? rcNoFromRow(Map row) { + final rc = (row['rc_no'] ?? + row['reg_no'] ?? + row['vehicle_number'] ?? + row['registration_no'] ?? + '') + .toString() + .trim(); + return rc.isEmpty ? null : rc; + } + + static String insuredNameFromRow(Map row) { + return (row['customer_name'] ?? + row['insured_name'] ?? + row['proposer_name'] ?? + row['name'] ?? + '') + .toString() + .trim(); + } + + static String policyNumberFromRow(Map row) { + return (row['policy_number'] ?? '').toString().trim(); + } + + /// Value for the "Search By Vehicle Number" input after selection. + static String vehicleInputValueOnSelect(Map row) { + final rc = rcNoFromRow(row); + if (rc != null && rc.isNotEmpty) return rc; + final policy = policyNumberFromRow(row); + return policy.isNotEmpty ? policy : ''; + } +} + +/// Dropdown list for vehicle or policy search — matches [AddClaimStepperDialog]. +class PolicySearchResultsList extends StatelessWidget { + static const Color _border = Color(0xFFE2E8F0); + static const Color _muted = Color(0xFF64748B); + static const Color _titleColor = Color(0xFF0F172A); + + const PolicySearchResultsList({ + super.key, + required this.rows, + required this.fromVehicle, + required this.onSelected, + this.height = 150, + this.borderRadius = 10, + }); + + final List> rows; + final bool fromVehicle; + final ValueChanged> onSelected; + final double height; + final double borderRadius; + + @override + Widget build(BuildContext context) { + if (rows.isEmpty) return const SizedBox.shrink(); + + return Container( + margin: const EdgeInsets.only(top: 6), + height: height, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: _border), + borderRadius: BorderRadius.circular(borderRadius), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 6, + ), + ], + ), + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: rows.length, + itemBuilder: (context, index) { + final row = rows[index]; + final policy = PolicySearchUtils.policyNumberFromRow(row); + final rcNo = PolicySearchUtils.rcNoFromRow(row) ?? ''; + final name = PolicySearchUtils.insuredNameFromRow(row); + final title = policy.isNotEmpty ? policy : rcNo; + + return InkWell( + onTap: () => onSelected(row), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (fromVehicle) ...[ + Text( + rcNo.isNotEmpty ? rcNo : '-', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: _titleColor, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (policy.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 2), + child: Text( + policy, + style: GoogleFonts.inter( + fontSize: 10, + color: _muted, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ] else ...[ + Text( + title.isNotEmpty ? title : '-', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: _titleColor, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (name.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 2), + child: Text( + name, + style: GoogleFonts.inter( + fontSize: 10, + color: _muted, + ), + ), + ), + ], + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/web/index.html b/web/index.html index 0ebea8b..504926f 100644 --- a/web/index.html +++ b/web/index.html @@ -15,8 +15,8 @@ the `--base-href` argument provided to `flutter build`. --> - Live build: also check env.dart (line 8) - + +