diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 7d85544..599dbea 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":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_app_check","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_app_check-0.4.0+1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.0.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/venba/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/home/venba/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.1.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/home/venba/.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":"/home/venba/.pub-cache/hosted/pub.dev/open_file_ios-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_app_check","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_app_check-0.4.0+1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.0.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.30/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/venba/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_android","path":"/home/venba/.pub-cache/hosted/pub.dev/google_sign_in_android-7.0.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_android","path":"/home/venba/.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":"/home/venba/.pub-cache/hosted/pub.dev/open_file_android-1.0.6/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_android-2.2.18/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_app_check","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_app_check-0.4.0+1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.0.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage_macos","path":"/home/venba/.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":"/home/venba/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.1.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/home/venba/.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":"/home/venba/.pub-cache/hosted/pub.dev/open_file_mac-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/open_file_linux-0.0.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.0.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/local_auth_windows-1.0.11/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/open_file_windows-0.0.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","dependencies":[],"dev_dependency":false},{"name":"firebase_app_check_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_app_check_web-0.2.0+16/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_auth_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth_web-6.0.2/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core_web-3.1.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_messaging_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging_web-4.0.1/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"flutter_secure_storage_web","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/venba/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","dependencies":[],"dev_dependency":false},{"name":"google_sign_in_web","path":"/home/venba/.pub-cache/hosted/pub.dev/google_sign_in_web-1.0.0/","dependencies":[],"dev_dependency":false},{"name":"open_file_web","path":"/home/venba/.pub-cache/hosted/pub.dev/open_file_web-0.0.4/","dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"firebase_app_check","dependencies":["firebase_app_check_web","firebase_core"]},{"name":"firebase_app_check_web","dependencies":["firebase_core","firebase_core_web"]},{"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":"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":"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":"2025-11-13 17:56:47.541149","version":"3.32.1","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":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_app_check","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_app_check-0.4.0+1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.0.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/venba/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/home/venba/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.1.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/home/venba/.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":"/home/venba/.pub-cache/hosted/pub.dev/open_file_ios-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_app_check","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_app_check-0.4.0+1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.0.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.30/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/venba/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_android","path":"/home/venba/.pub-cache/hosted/pub.dev/google_sign_in_android-7.0.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_android","path":"/home/venba/.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":"/home/venba/.pub-cache/hosted/pub.dev/open_file_android-1.0.6/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_android-2.2.18/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_app_check","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_app_check-0.4.0+1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.0.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.1/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage_macos","path":"/home/venba/.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":"/home/venba/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.1.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/home/venba/.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":"/home/venba/.pub-cache/hosted/pub.dev/open_file_mac-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/open_file_linux-0.0.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.0.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/local_auth_windows-1.0.11/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/open_file_windows-0.0.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"connectivity_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/home/venba/.pub-cache/hosted/pub.dev/file_picker-10.3.3/","dependencies":[],"dev_dependency":false},{"name":"firebase_app_check_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_app_check_web-0.2.0+16/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_auth_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth_web-6.0.2/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core_web-3.1.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_messaging_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging_web-4.0.1/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"flutter_secure_storage_web","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/home/venba/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","dependencies":[],"dev_dependency":false},{"name":"google_sign_in_web","path":"/home/venba/.pub-cache/hosted/pub.dev/google_sign_in_web-1.0.0/","dependencies":[],"dev_dependency":false},{"name":"open_file_web","path":"/home/venba/.pub-cache/hosted/pub.dev/open_file_web-0.0.4/","dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/home/venba/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"firebase_app_check","dependencies":["firebase_app_check_web","firebase_core"]},{"name":"firebase_app_check_web","dependencies":["firebase_core","firebase_core_web"]},{"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":"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":"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":"2025-11-18 18:55:21.455544","version":"3.32.1","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/lib/core/services/api_service.dart b/lib/core/services/api_service.dart index 032ad7a..f71f2d8 100644 --- a/lib/core/services/api_service.dart +++ b/lib/core/services/api_service.dart @@ -7,6 +7,8 @@ import 'package:nhance_partner/data/services/auth_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; import '../../data/utils/toastNotification.dart'; +import '../../presentation/screens/staff/enquiry_single_page/model/dropdown_option.dart'; +import '../../presentation/screens/staff/enquiry_single_page/model/enquiry_model.dart'; import '../config/env.dart'; import 'package:universal_html/html.dart' as html; import 'package:http/http.dart' as http; @@ -1079,4 +1081,89 @@ class ApiService { print('fetchAGENTNameDropDown 4 - $response'); return response; } + + static late String _baseUrl; + + static void initialize(String baseUrl) { + _baseUrl = Env.apiUrl; + } + + // Fetch all enquiries + static Future> fetchEnquiries() async { + try { + final response = await http.get( + Uri.parse('$_baseUrl/api/enquiries'), + headers: {'Content-Type': 'application/json'}, + ); + + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((json) => EnquiryModel.fromJson(json)).toList(); + } else { + throw Exception('Failed to load enquiries'); + } + } catch (e) { + throw Exception('Error fetching enquiries: $e'); + } + } + + // Create new enquiry + static Future createEnquiry(EnquiryModel enquiry) async { + try { + final response = await http.post( + Uri.parse('$_baseUrl/api/enquiries'), + headers: {'Content-Type': 'application/json'}, + body: json.encode(enquiry.toJson()), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + return EnquiryModel.fromJson(json.decode(response.body)); + } else { + throw Exception('Failed to create enquiry'); + } + } catch (e) { + throw Exception('Error creating enquiry: $e'); + } + } + + // Update enquiry + static Future updateEnquiry( + String id, + EnquiryModel enquiry, + ) async { + try { + final response = await http.put( + Uri.parse('$_baseUrl/api/enquiries/$id'), + headers: {'Content-Type': 'application/json'}, + body: json.encode(enquiry.toJson()), + ); + + if (response.statusCode == 200) { + return EnquiryModel.fromJson(json.decode(response.body)); + } else { + throw Exception('Failed to update enquiry'); + } + } catch (e) { + throw Exception('Error updating enquiry: $e'); + } + } + + // Fetch dropdown options + static Future> fetchDropdownOptions(String type) async { + try { + final response = await http.get( + Uri.parse('$_baseUrl/api/options/$type'), + headers: {'Content-Type': 'application/json'}, + ); + + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((json) => DropdownOption.fromJson(json)).toList(); + } else { + throw Exception('Failed to load options'); + } + } catch (e) { + throw Exception('Error fetching options: $e'); + } + } } diff --git a/lib/main.dart b/lib/main.dart index 8b43932..10f714f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:nhance_partner/presentation/providers/manager_provider.dart'; import 'package:nhance_partner/presentation/providers/userRoleProvider.dart'; +import 'package:nhance_partner/presentation/screens/staff/enquiry_single_page/widgets/enquiry_screen.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'core/routing/app_router.dart'; @@ -110,6 +111,21 @@ class _MyAppState extends ConsumerState { @override Widget build(BuildContext context) { + // return MaterialApp( + // title: 'Nhance Partner', + // debugShowCheckedModeBanner: false, + // theme: ThemeData( + // primaryColor: const Color(0xFFb8e6e1), + // colorScheme: ColorScheme.fromSeed( + // seedColor: const Color(0xFFb8e6e1), + // primary: const Color(0xFF4a90e2), + // ), + // scaffoldBackgroundColor: const Color(0xFFf5f5f5), + // useMaterial3: true, + // ), + // home: const EnquiryScreen(), + // ); + return MaterialApp.router( debugShowCheckedModeBanner: false, title: 'Nhance Partner', @@ -126,6 +142,7 @@ class _MyAppState extends ConsumerState { Theme.of(context).textTheme, // base on default styles ), ), + routerConfig: appRouter, ); } diff --git a/lib/presentation/layouts/aphed b/lib/presentation/layouts/aphed new file mode 100644 index 0000000..8694478 --- /dev/null +++ b/lib/presentation/layouts/aphed @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; + +class AppHeader extends StatelessWidget implements PreferredSizeWidget { + const AppHeader({super.key}); + + @override + Size get preferredSize => const Size.fromHeight(60); + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: const Color(0xFFb8e6e1), + elevation: 2, + title: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Nhance', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Color(0xFF00acc1), + ), + ), + const Text( + 'Partner', + style: TextStyle(fontSize: 10, color: Color(0xFF666666)), + ), + ], + ), + ], + ), + actions: [ + TextButton( + onPressed: () {}, + child: const Text( + 'Dashboard', + style: TextStyle(color: Colors.black87), + ), + ), + TextButton( + onPressed: () {}, + child: const Text('Enquiry', style: TextStyle(color: Colors.black87)), + ), + PopupMenuButton( + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Text('Users', style: TextStyle(color: Colors.black87)), + Icon(Icons.arrow_drop_down, color: Colors.black87), + ], + ), + ), + itemBuilder: (context) => [ + const PopupMenuItem(value: 'staff', child: Text('Staff')), + const PopupMenuItem(value: 'agent', child: Text('Agent')), + ], + ), + PopupMenuButton( + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Text('Reports', style: TextStyle(color: Colors.black87)), + Icon(Icons.arrow_drop_down, color: Colors.black87), + ], + ), + ), + itemBuilder: (context) => [ + const PopupMenuItem(value: 'claims', child: Text('Claims')), + const PopupMenuItem( + value: 'endorsement', + child: Text('Endorsement'), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: PopupMenuButton( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6), + ), + child: Row( + children: [ + const Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'John Doe', + style: TextStyle(fontWeight: FontWeight.w600), + ), + Text( + 'Administrator', + style: TextStyle(fontSize: 11, color: Colors.grey), + ), + ], + ), + const SizedBox(width: 10), + CircleAvatar( + radius: 18, + backgroundColor: const Color(0xFF4a90e2), + child: const Text( + 'JD', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + itemBuilder: (context) => [ + const PopupMenuItem( + value: 'profile', + child: Text('👤 Profile Details'), + ), + const PopupMenuItem(value: 'logout', child: Text('🚪 Logout')), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/layouts/appheader.dart b/lib/presentation/layouts/appheader.dart new file mode 100644 index 0000000..fbcebd4 --- /dev/null +++ b/lib/presentation/layouts/appheader.dart @@ -0,0 +1,545 @@ +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:jwt_decode/jwt_decode.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../core/routing/routes.dart'; +import '../../core/services/api_service.dart'; +import '../../data/services/auth_service.dart'; +import '../providers/manager_provider.dart'; +import '../providers/userRoleProvider.dart'; +import '../screens/UserManagement/Profile/profile_web.dart'; + +class AppHeader extends ConsumerStatefulWidget implements PreferredSizeWidget { + const AppHeader({super.key}); + + @override + ConsumerState createState() => _AppHeaderState(); + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} + +class _AppHeaderState extends ConsumerState { + String? role; + ApiService apiService = ApiService(); + Map? profileData; + String? _token; + OverlayEntry? _overlayEntry; + + @override + void initState() { + super.initState(); + _loadUser(); + _initializeToken(); + } + + @override + void dispose() { + _hidePopup(); + super.dispose(); + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + if (_token != null) { + final Map decodedToken = Jwt.parseJwt(_token!); + setState(() { + profileData = decodedToken['data']; + }); + } + } + + Future _loadUser() async { + final prefs = await SharedPreferences.getInstance(); + setState(() { + role = prefs.getString('userRole') ?? "Guest"; + }); + print('AppHeader Role - $role'); + } + + // Clear dashboard filters + Future _clearDashboardFilters() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('dashboardKeyProvider'); + await prefs.remove('dashboardStatusProvider'); + await prefs.remove('dashboardStaffIdProvider'); + } + + // Show hover popup + void _showPopup(BuildContext context, Offset offset, Size size, String key) { + _hidePopup(); + + _overlayEntry = OverlayEntry( + builder: (_) { + return Positioned( + left: offset.dx, + top: offset.dy + size.height + 8, + child: MouseRegion( + onExit: (_) => _hidePopup(), + child: Material( + elevation: 8, + shadowColor: Colors.black26, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + border: Border.all(color: Colors.grey.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (key == 'User') ...[ + _buildPopupItem( + label: "Partner", + onTap: () { + _hidePopup(); + context.go(AppRoutes.agentLst); + }, + ), + const SizedBox(height: 2), + _buildPopupItem( + label: "Staff", + onTap: () { + _hidePopup(); + context.go(AppRoutes.staffLst); + }, + ), + ], + if (key == 'Reports') ...[ + if (role == 'manager') ...[ + _buildPopupItem( + label: "Attendance", + onTap: () { + _hidePopup(); + context.go(AppRoutes.allStaffAttendance); + }, + ), + ], + SizedBox(height: 2), + _buildPopupItem( + label: "Claims", + onTap: () { + _hidePopup(); + context.go(AppRoutes.claimlist); + }, + ), + const SizedBox(height: 2), + _buildPopupItem( + label: "Endorsement", + onTap: () { + _hidePopup(); + context.go(AppRoutes.endosement); + }, + ), + const SizedBox(height: 2), + _buildPopupItem( + label: "Policy", + onTap: () { + _hidePopup(); + context.go(AppRoutes.policylist); + }, + ), + ], + ], + ), + ), + ), + ), + ); + }, + ); + + Overlay.of(context, rootOverlay: true).insert(_overlayEntry!); + } + + void _hidePopup() { + _overlayEntry?.remove(); + _overlayEntry = null; + } + + @override + Widget build(BuildContext context) { + final roleId = ref.watch(userRoleProvider); + + return AppBar( + backgroundColor: const Color(0xFFD6F6F4), + elevation: 0, + automaticallyImplyLeading: false, + titleSpacing: 24, + toolbarHeight: 64, + title: Row( + children: [ + // Logo + Image.asset( + "assets/login/nhance-partner-logo.png", + height: 45, + width: 95, + ), + + Spacer(), + + // Dashboard + _buildMenuItem( + icon: Icons.dashboard, + label: "Dashboard", + onTap: () async { + _hidePopup(); + await _clearDashboardFilters(); + context.go(AppRoutes.dashboard); + }, + ), + + const SizedBox(width: 15), + + // Enquiry + _buildMenuItem( + icon: Icons.list_alt_rounded, + label: "Enquiry", + onTap: () async { + _hidePopup(); + await _clearDashboardFilters(); + if (roleId == 'agent') { + context.go(AppRoutes.enquiryLst); + } else { + context.go(AppRoutes.enquiryForStaff); + } + }, + ), + + const SizedBox(width: 8), + + // User (Manager Only) - with hover popup + if (roleId == 'manager') ...[ + _buildHoverMenuItem( + icon: Icons.person_add_alt, + label: "User", + popupKey: 'User', + ), + const SizedBox(width: 8), + ], + + // Reports (Not Staff) - with hover popup + if (roleId != 'staff') ...[ + _buildHoverMenuItem( + icon: Icons.receipt_long, + label: "Reports", + popupKey: 'Reports', + ), + const SizedBox(width: 8), + ], + + const Spacer(), + + // Raise Enquiry button for agents + if (role == 'agent') ...[ + Container( + height: 36, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFF425B5B), + borderRadius: BorderRadius.circular(6), + ), + child: InkWell( + onTap: () async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('enqAgentDataId'); + ref.read(enquiryIdProvider.notifier).state = null; + context.go(AppRoutes.tabEnquiry); + }, + child: Center( + child: Text( + 'Raise Enquiry', + style: GoogleFonts.inter( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + ), + ), + ), + const SizedBox(width: 16), + ], + + // Notifications button + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade200, width: 1), + ), + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon( + Icons.notifications_none, + color: Colors.black87, + size: 20, + ), + onPressed: () { + debugPrint("Notifications tapped"); + }, + tooltip: 'Notifications', + ), + ), + + const SizedBox(width: 12), + + // Profile with popup + PopupMenuButton( + color: Colors.white, + offset: const Offset(0, 52), + child: Container( + height: 36, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + // border: Border.all(color: Colors.grey.shade200, width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CircleAvatar( + radius: 14, + // backgroundColor: const Color(0xFF4a90e2), + backgroundColor: const Color(0xFF2E7D6E), + child: Text( + profileData?['name']?.substring(0, 2).toUpperCase() ?? + 'P', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 10, + ), + ), + ), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + profileData?['name'] ?? 'Manager', + style: GoogleFonts.inter( + fontWeight: FontWeight.w600, + fontSize: 11, + color: Colors.black87, + ), + ), + Text( + (role == 'agent') ? 'partner' : role ?? 'manager', + style: GoogleFonts.inter( + fontSize: 9, + color: Colors.grey.shade600, + ), + ), + ], + ), + const SizedBox(width: 4), + Icon( + Icons.keyboard_arrow_down, + size: 16, + color: Colors.grey.shade700, + ), + ], + ), + ), + itemBuilder: (context) => [ + PopupMenuItem( + enabled: false, + + child: Container( + width: 480, + color: Colors.white, + child: ProfilePopUp(), + ), + ), + ], + ), + + const SizedBox(width: 12), + + // Logout button + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade200, width: 1), + ), + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.logout, color: Colors.black87, size: 18), + onPressed: () async { + debugPrint("Logout tapped"); + AuthService.clearToken(); + if (role != 'handler') { + await apiService.logoutUsingAPI(context); + } + context.go(AppRoutes.login); + }, + tooltip: 'Logout', + ), + ), + ], + ), + ); + } + + // Simple menu item widget + Widget _buildMenuItem({ + required IconData icon, + required String label, + required VoidCallback onTap, + }) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(6), + child: MouseRegion( + onEnter: (_) => _hidePopup(), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(6)), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18, color: Colors.black87), + const SizedBox(width: 6), + Text( + label, + style: GoogleFonts.inter( + color: Colors.black87, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ); + } + + // Menu item with hover popup + Widget _buildHoverMenuItem({ + required IconData icon, + required String label, + required String popupKey, + }) { + return Builder( + builder: (itemContext) { + return MouseRegion( + onEnter: (_) { + final renderBox = itemContext.findRenderObject() as RenderBox; + final offset = renderBox.localToGlobal(Offset.zero); + final size = renderBox.size; + _showPopup(context, offset, size, popupKey); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(6)), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18, color: Colors.black87), + const SizedBox(width: 6), + Text( + label, + style: GoogleFonts.inter( + color: Colors.black87, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 2), + Icon(Icons.arrow_drop_down, color: Colors.black87, size: 18), + ], + ), + ), + ); + }, + ); + } + + // Popup item widget + Widget _buildPopupItem({required String label, required VoidCallback onTap}) { + return _HoverableContainer( + label: label, + onTap: onTap, + normalColor: Colors.white, + hoverColor: const Color(0xFFF5F5F5), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(64); +} + +// Hoverable container for popup items +class _HoverableContainer extends StatefulWidget { + final String label; + final VoidCallback onTap; + final Color normalColor; + final Color hoverColor; + + const _HoverableContainer({ + required this.label, + required this.onTap, + required this.normalColor, + required this.hoverColor, + }); + + @override + _HoverableContainerState createState() => _HoverableContainerState(); +} + +class _HoverableContainerState extends State<_HoverableContainer> { + bool _isHovered = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: InkWell( + onTap: widget.onTap, + child: Container( + width: 140, + decoration: BoxDecoration( + color: _isHovered ? widget.hoverColor : widget.normalColor, + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.label, + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + Icon( + Icons.arrow_forward_ios_rounded, + size: 10, + color: Colors.grey.shade600, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/layouts/main_layout.dart b/lib/presentation/layouts/main_layout.dart index 0f27a73..5266a84 100644 --- a/lib/presentation/layouts/main_layout.dart +++ b/lib/presentation/layouts/main_layout.dart @@ -12,6 +12,7 @@ import '../widgets/topbar.dart'; import '../widgets/footer.dart'; import '../widgets/drawer_menu.dart'; import '../widgets/mobile_tabs.dart'; +import 'appheader.dart'; import 'responsive_layout.dart'; import 'package:http/http.dart' as http; // import 'package:http/http.dart' as ref; @@ -90,70 +91,72 @@ class MainLayoutState extends ConsumerState { // Web Layout web: Scaffold( - appBar: TopBar( - title: widget.title, - onMenuPressed: () { - // Scaffold.of(context).openDrawer(); - // Scaffold.of(context).openDrawer(); - - setState(() { - isDrawerOpen = !isDrawerOpen; - }); - }, - onLogout: () async { - debugPrint("Logout tapped"); - AuthService.clearToken(); - if (role != 'handler') { - await apiService.logoutUsingAPI(context); - } - context.go(AppRoutes.login); - }, - - onProfile: (TapDownDetails details) { - final RenderBox overlay = - Overlay.of(context).context.findRenderObject() as RenderBox; - - showMenu( - context: context, - color: Colors.white, - position: RelativeRect.fromRect( - details.globalPosition & const Size(40, 40), - Offset.zero & overlay.size, - ), - items: [ - PopupMenuItem( - enabled: false, - child: SizedBox(width: 80, child: ProfilePopUp()), - ), - ], - ); - }, - - onNotifications: () { - debugPrint("Notifications tapped"); - }, - ), + // appBar: TopBar( + // title: widget.title, + // onMenuPressed: () { + // // Scaffold.of(context).openDrawer(); + // // Scaffold.of(context).openDrawer(); + // + // setState(() { + // isDrawerOpen = !isDrawerOpen; + // }); + // }, + // onLogout: () async { + // debugPrint("Logout tapped"); + // AuthService.clearToken(); + // if (role != 'handler') { + // await apiService.logoutUsingAPI(context); + // } + // context.go(AppRoutes.login); + // }, + // + // onProfile: (TapDownDetails details) { + // final RenderBox overlay = + // Overlay.of(context).context.findRenderObject() as RenderBox; + // + // showMenu( + // context: context, + // + // color: Colors.white, + // position: RelativeRect.fromRect( + // details.globalPosition & const Size(40, 40), + // Offset.zero & overlay.size, + // ), + // items: [ + // PopupMenuItem( + // enabled: false, + // child: SizedBox(width: 480, child: ProfilePopUp()), + // ), + // ], + // ); + // }, + // + // onNotifications: () { + // debugPrint("Notifications tapped"); + // }, + // ), + appBar: AppHeader(), body: Row( children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 250), - width: isDrawerOpen ? 100 : 0, // 👈 adjust width - child: isDrawerOpen - ? const DrawerMenu() - : const SizedBox.shrink(), // hides drawer - ), - + // AnimatedContainer( + // duration: const Duration(milliseconds: 250), + // width: isDrawerOpen ? 100 : 0, // 👈 adjust width + // child: isDrawerOpen + // ? const DrawerMenu() + // : const SizedBox.shrink(), // hides drawer + // ), Expanded( child: Container( - padding: const EdgeInsets.all(8.0), - color: Colors.white, + padding: const EdgeInsets.all(16.0), + color: Color(0xFFFCFDFD), + // color: Color(0xffF8FAFB), + // color: Colors.white, child: Column(children: [Expanded(child: widget.body)]), ), ), ], ), ), - // body: Row( // children: [ // const SizedBox( diff --git a/lib/presentation/screens/UserManagement/Profile/profile_web.dart b/lib/presentation/screens/UserManagement/Profile/profile_web.dart index 37e27df..ce22f69 100644 --- a/lib/presentation/screens/UserManagement/Profile/profile_web.dart +++ b/lib/presentation/screens/UserManagement/Profile/profile_web.dart @@ -164,6 +164,8 @@ class ProfilePopUpState extends ConsumerState { return Container( padding: const EdgeInsets.all(6), width: 450, + color: Colors.white, + // color: Colors.white, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -180,7 +182,8 @@ class ProfilePopUpState extends ConsumerState { width: 25, height: 25, decoration: BoxDecoration( - color: const Color(0xFFF1F1F1), + color: Colors.white, + // color: const Color(0xFFF1F1F1), borderRadius: BorderRadius.circular(10.0), ), child: InkWell( @@ -212,7 +215,8 @@ class ProfilePopUpState extends ConsumerState { width: 70, height: 70, decoration: BoxDecoration( - color: Color(0xFFD9D9D9), + // color: Color(0xFFD9D9D9), + color: Colors.white, borderRadius: BorderRadius.circular(30.0), ), child: Icon( @@ -429,7 +433,7 @@ class ProfilePopUpState extends ConsumerState { static const _topheaderStyle = TextStyle( color: Colors.black, fontWeight: FontWeight.w600, - fontSize: 19, + fontSize: 18, ); } diff --git a/lib/presentation/screens/dashboard/dashboard.dart b/lib/presentation/screens/dashboard/dashboard.dart index e50e655..8008b0c 100644 --- a/lib/presentation/screens/dashboard/dashboard.dart +++ b/lib/presentation/screens/dashboard/dashboard.dart @@ -1854,32 +1854,29 @@ class UnassignedEnq extends StatelessWidget { focusColor: Color(0xFF3E5B56), highlightColor: Color(0xFF3E5B56), borderRadius: BorderRadius.circular(2), // for ripple effect - // border: Border.all( - // color: Colors.green , - // width: 2, - // ), - onTap: (role == 'handler') - ? () { - // Print the id when row is clicked - print("Clicked ID fd: ${row['id']}"); - // You can also navigate or perform any action here - - showDialog( - context: context, - builder: (ctx) => AssignStaffDialog( - enquiryPrimaryId: row['id'], - regNum: row['reg_no'], - userId: 1, - onSubmit: (value) { - debugPrint("New assignY: $value"); - if (onRefresh != null) { - onRefresh!(); // ✅ call the parent's refresh - } - }, - ), - ); - } - : null, + onTap: null, + // onTap: (role == 'handler') + // ? () { + // // Print the id when row is clicked + // print("Clicked ID fd: ${row['id']}"); + // // You can also navigate or perform any action here + // + // showDialog( + // context: context, + // builder: (ctx) => AssignStaffDialog( + // enquiryPrimaryId: row['id'], + // regNum: row['reg_no'], + // userId: 1, + // onSubmit: (value) { + // debugPrint("New assignY: $value"); + // if (onRefresh != null) { + // onRefresh!(); // ✅ call the parent's refresh + // } + // }, + // ), + // ); + // } + // : null, child: Container( // height: 5200, margin: const EdgeInsets.symmetric(vertical: 5), diff --git a/lib/presentation/screens/handler/enquiryList.dart b/lib/presentation/screens/handler/enquiryList.dart index 5e5ffcb..dd10c2e 100644 --- a/lib/presentation/screens/handler/enquiryList.dart +++ b/lib/presentation/screens/handler/enquiryList.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; @@ -8,7 +10,7 @@ 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/handler/quotationPopUp.dart'; + import 'package:shared_preferences/shared_preferences.dart'; import '../../../../core/routing/routes.dart'; @@ -36,6 +38,7 @@ import '../../themes/indicators/text_field_theme_inline_editor.dart' hide UpperCaseTextFormatter; import '../../widgets/custom_Stdate_EnDate_Filter.dart'; import '../../widgets/custom_action_popup.dart'; +import '../staff/Enquiry/quotationPopUpAccptReject.dart'; import '../staff/assignStaff.dart'; class EnquiryListHandler extends ConsumerStatefulWidget { @@ -2418,56 +2421,74 @@ class EnquiryHandlerState extends ConsumerState { // Build both-direction scrollable DataTable return LayoutBuilder( builder: (context, constraints) { - double minWidth = isDesktop ? constraints.maxWidth : 1300; - - return SingleChildScrollView( - scrollDirection: Axis.horizontal, + double minWidth = constraints.maxWidth < 1300 + ? 1300 + : constraints.maxWidth; + return ScrollConfiguration( + behavior: const MaterialScrollBehavior().copyWith( + dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch}, + ), child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: minWidth), - child: DataTable( - headingRowColor: WidgetStatePropertyAll(Color(0xFFEDF6F5)), - dividerThickness: 0.5, - headingRowHeight: 30, - columnSpacing: isDesktop ? 20.0 : 16.0, - border: TableBorder( - horizontalInside: BorderSide( - width: 0.5, - color: Colors.grey.shade200, + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth), + child: DataTable( + headingRowColor: WidgetStatePropertyAll(Color(0xFFEDF6F5)), + dividerThickness: 0.5, + headingRowHeight: 30, + columnSpacing: isDesktop ? 20.0 : 16.0, + border: TableBorder( + horizontalInside: BorderSide( + width: 0.5, + color: Colors.grey.shade200, + ), ), + columns: const [ + // DataColumn(label: Text('S.No', style: _headerStyle)), + DataColumn( + label: Text('Received Date', style: _headerStyle), + ), + DataColumn(label: Text('Partner *', style: _headerStyle)), + DataColumn( + label: Text('Assigned To *', style: _headerStyle), + ), + DataColumn(label: Text('Broker *', style: _headerStyle)), + DataColumn(label: Text('Insurer *', style: _headerStyle)), + DataColumn( + label: Text('Insured Name *', style: _headerStyle), + ), + DataColumn( + label: Text('Vehicle No *', style: _headerStyle), + ), + DataColumn( + label: Text('Vehicle Type *', style: _headerStyle), + ), + DataColumn(label: Text('Email', style: _headerStyle)), + DataColumn(label: Text('Mobile', style: _headerStyle)), + DataColumn(label: Text('Documents', style: _headerStyle)), + DataColumn(label: Text('Remarks', style: _headerStyle)), + DataColumn(label: Text('Action', style: _headerStyle)), + DataColumn( + label: Text('Assigned Date', style: _headerStyle), + ), + DataColumn(label: Text('Premium', style: _headerStyle)), + DataColumn( + label: Text('Payment Mode', style: _headerStyle), + ), + DataColumn( + label: Text('Policy Number', style: _headerStyle), + ), + DataColumn(label: Text('Status', style: _headerStyle)), + ], + rows: sortedData.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + final sno = startIndex + index + 1; + return _buildDataRow(item, sno); + }).toList(), ), - columns: const [ - // DataColumn(label: Text('S.No', style: _headerStyle)), - DataColumn(label: Text('Received Date', style: _headerStyle)), - DataColumn(label: Text('Partner *', style: _headerStyle)), - DataColumn(label: Text('Assigned To *', style: _headerStyle)), - DataColumn(label: Text('Broker *', style: _headerStyle)), - DataColumn(label: Text('Insurer *', style: _headerStyle)), - DataColumn( - label: Text('Insured Name *', style: _headerStyle), - ), - DataColumn(label: Text('Vehicle No *', style: _headerStyle)), - DataColumn( - label: Text('Vehicle Type *', style: _headerStyle), - ), - DataColumn(label: Text('Email', style: _headerStyle)), - DataColumn(label: Text('Mobile', style: _headerStyle)), - DataColumn(label: Text('Documents', style: _headerStyle)), - DataColumn(label: Text('Remarks', style: _headerStyle)), - DataColumn(label: Text('Action', style: _headerStyle)), - DataColumn(label: Text('Assigned Date', style: _headerStyle)), - DataColumn(label: Text('Premium', style: _headerStyle)), - DataColumn(label: Text('Payment Mode', style: _headerStyle)), - DataColumn(label: Text('Policy Number', style: _headerStyle)), - DataColumn(label: Text('Status', style: _headerStyle)), - ], - rows: sortedData.asMap().entries.map((entry) { - final index = entry.key; - final item = entry.value; - final sno = startIndex + index + 1; - return _buildDataRow(item, sno); - }).toList(), ), ), ), diff --git a/lib/presentation/screens/login/login_screen.dart b/lib/presentation/screens/login/login_screen.dart index b8f75e9..c9c1876 100644 --- a/lib/presentation/screens/login/login_screen.dart +++ b/lib/presentation/screens/login/login_screen.dart @@ -263,9 +263,9 @@ class _LoginScreenState extends ConsumerState { final message = data['data']['message']; - setState(() { - enteredEmailOrMobile = true; - }); + // setState(() { + // enteredEmailOrMobile = true; + // }); if (verification == true) { // if (switcherStatus == 1) { diff --git a/lib/presentation/screens/staff/Enquiry/enquiry_inline_Edit_17Nov.dart b/lib/presentation/screens/staff/Enquiry/enquiry_inline_Edit_17Nov.dart new file mode 100644 index 0000000..dbc3275 --- /dev/null +++ b/lib/presentation/screens/staff/Enquiry/enquiry_inline_Edit_17Nov.dart @@ -0,0 +1,3594 @@ +import 'dart:ui'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/cupertino.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/staff/Enquiry/quotationPopUpAccptReject.dart'; +import 'package:nhance_partner/presentation/screens/staff/Enquiry/raise_enq_form.dart'; +import 'package:nhance_partner/presentation/screens/staff/Enquiry/tabs/tab.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../../core/config/env.dart'; +import '../../../../core/routing/routes.dart'; + +import '../../../../core/services/api_service.dart'; +import 'package:universal_html/html.dart' as html; + +import '../../../../data/services/auth_service.dart'; +import '../../../../data/utils/Pagination.dart'; +import '../../../../data/utils/toastNotification.dart'; +import '../../../../data/utils/validators.dart'; +import '../../../layouts/main_layout.dart'; +import '../../../layouts/responsive_layout.dart'; +import '../../../providers/manager_provider.dart'; +import '../../../providers/quotation_staff_proivder.dart'; +import '../../../providers/userRoleProvider.dart'; +import '../../../themes/indicators/customizd_file_upload.dart'; +import '../../../themes/indicators/export_btn.dart'; +import '../../../themes/indicators/input_field_decoration.dart'; +import '../../../themes/indicators/search_field_theme.dart'; +import '../../../themes/indicators/text_field_theme.dart'; +import '../../../themes/indicators/text_field_theme_inline_editor.dart' + hide UpperCaseTextFormatter; +import '../../../widgets/custom_Stdate_EnDate_Filter.dart'; +import '../assignStaff.dart'; + +class EnquiryListStaffInline extends ConsumerStatefulWidget { + const EnquiryListStaffInline({super.key}); + @override + ConsumerState createState() => EnquiryStaffState(); +} + +class EnquiryStaffState extends ConsumerState { + int currentPage = 1; + int itemsPerPage = 10; + late ApiService apiService; + dynamic userId; + dynamic idPrimary; + dynamic managerId; + dynamic dashboardKey; + dynamic SelectedStatus; + dynamic SelectedStaffId; + dynamic handlerId; + String? _token; + final _formKey = GlobalKey(); + Map fieldErrors = {}; + + final raiseEnqKey = GlobalKey(); + + // List> dataVal = []; + List> getStaffData = []; + List> originalData = []; + List> filteredData = []; + bool isLoading = false; + dynamic roleId; + bool _showFilterRow = false; + + Map rowExpanded = {}; + Map controllers = {}; + List tabHeader = [ + 'startDate', + 'endDate', + 'name', + 'email', + 'mobile', + 'code', + 'address', + 'regNo', + 'remarks', + ]; + + Map isEditingRow = {}; + Map> rowControllers = {}; + + //---------------------- Enquiry TAB Initializations Starts -------------------------------- // + + List> getVehicleTypeData = []; + List> filteredVechicleData = []; + + List> getAgentListData = []; + List> filteredAgentData = []; + + List> getInsurersData = []; + List> filteredInsurersData = []; + + String? selectedVehicleType; + String? selectedInsurer; + String? selectedAgent; + PlatformFile? docUploadedRCFile; + PlatformFile? docUploadedIDProof; + PlatformFile? docUploadedPrevPolicy; + + String? selectedRCFile; + String? selectedIdProof; + String? selectedPrevPolicy; + + String? selectedRCFileName; + + String? rcFileUrlFromApi; + String? idProofFileUrlFromApi; + String? prevPolicyFileUrlFromApi; + + String? selectedId; + + Map dataDetails() { + final data = { + "agent_id": ((roleId == 'handler') || (roleId == 'manager')) + ? selectedAgent + : userId, + "name": controllers["name"]?.text, + "mobile": controllers["mobile"]?.text, + "email": controllers["email"]?.text, + "reg_no": controllers["regNo"]?.text, + "vehicle_type_id": selectedVehicleType, + "is_data_created_by_handler": roleId == 'handler' ? '1' : '0', + "is_data_created_by_manager": roleId == 'manager' ? '1' : '0', + // "insurer_id": selectedInsurer, + // "rc_file_name": "rc_doc.pdf", + // "id_proof_file_name": "id_proof.pdf", + // "previous_policy_file_name": "previous_policy.pdf", + "remarks": controllers["remarks"]?.text, + "assigned_to": selectedStaff, + "insurer_id": selectedInsurer, + "broker_id": selectedBroker, + "manager_id": managerId, + "created_by": userId, + }; + + return data; + } + + final GlobalKey>> dropDownKey = + GlobalKey>>(); + final GlobalKey>> + dropDownKeyInsurer = GlobalKey>>(); + final GlobalKey>> dropDownKeyAgent = + GlobalKey>>(); + //---------------------- Assign Staff starts -------------------------------- // + + final GlobalKey>> + dropDownKeyInsurerEnqAsgn = + GlobalKey>>(); + + final GlobalKey>> dropDownKeyBroker = + GlobalKey>>(); + final GlobalKey>> + dropDownSelectStaffKey = + GlobalKey>>(); + + List> getStaffDetailsDataEnqAsgn = []; + List> filteredStaffDataEnqAsgn = []; + + String? selectedStaff; + String? selectedRegNum; + String? selectedStaffName; + List> getBrokerData = []; + List> filteredBrokerData = []; + String? selectedBroker; + + //---------------------- Assign Staff Ends -------------------------------- // + //---------------------- Enquiry TAB Initializations Ends -------------------------------- // + + @override + void initState() { + super.initState(); + + apiService = ApiService(); + + for (String field in tabHeader) { + controllers[field] = TextEditingController(); + } + _initializeToken(); + + Future.microtask(() async { + // final id = ref.read(managerIdProvider); + roleId = ref.read(userRoleProvider); + userId = ref.read(userIdProvider); + managerId = ref.read(managerIdProvider); + + final prefs = await SharedPreferences.getInstance(); + dashboardKey = prefs.getString('dashboardKeyProvider'); + final dashboardStatus = prefs.getString('dashboardStatusProvider'); + final dashboardStaffId = prefs.getString('dashboardStaffIdProvider'); + + print('handlerIdENQ - $handlerId'); + print("C72 => r : $roleId | uId: $userId !mID : $managerId "); + + print('dashboardKey - $dashboardKey'); + print('dashboardStatus - $dashboardStatus'); + print('dashboardStaffId - $dashboardStaffId'); + + if (managerId != null && + dashboardKey != null && + dashboardStatus != null && + dashboardStaffId != null) { + print("DASHBOARD STATus-$dashboardStatus -- $dashboardStaffId -"); + SelectedStatus = dashboardStatus; + SelectedStaffId = dashboardStaffId; + getStaffList( + managerId, + roleId, + SelectedStatus: dashboardStatus, + SelectedStaffId: dashboardStaffId, + ); + } else { + print('ELSE'); + getStaffList(managerId, roleId); + } + if (managerId != null) { + print('managerId - $managerId'); + getAgentList(managerId); + } + + final userID = ref.watch(userIdProvider); + + print("managerId - $managerId"); + if (userID != null) { + print('hansles'); + getStaffDetailsForEnquiryAssignment(userID); + } + }); + + getVehicleType(); + getInsurers(); + getBroker(); + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + print("APISERTOKEN - $_token"); + } + + void refresh() { + final id = ref.read(managerIdProvider); + final roleId = ref.read(userRoleProvider); + final userId = ref.read(userIdProvider); + + print("C82 => r : $roleId | mId: $id | uId: $userId "); + if (managerId != null) { + getStaffList(managerId, roleId); + } + } + + void filterDateRange() { + // ✅ Call your API + getStaffList( + managerId, + roleId, + SelectedStatus: SelectedStatus ?? '', + SelectedStaffId: SelectedStaffId ?? '', + ); + } + + Future refrshfilterDateRange() async { + print('refrshfilterDateRange'); + // setState(() async { + final prefs = await SharedPreferences.getInstance(); + + // ✅ Clear any old data + await prefs.remove('dashboardKeyProvider'); + await prefs.remove('dashboardStatusProvider'); + await prefs.remove('dashboardStaffIdProvider'); + SelectedStatus = ''; + SelectedStaffId = null; + controllers['startDate']!.clear(); + controllers['endDate']!.clear(); + + controllers['startDate']?.text = ''; + controllers['endDate']?.text = ''; + // Reset the FormField validation + _formKey.currentState?.reset(); + // }); + + final int parsedManagerId = int.tryParse(managerId.toString()) ?? 0; + print('refrshfilterDateRange 1'); + getStaffList( + // managerId, + parsedManagerId, + roleId, + SelectedStatus: SelectedStatus ?? '', + SelectedStaffId: SelectedStaffId ?? '', + ); + print('refrshfilterDateRange 2'); + } + + Future getInsurers() async { + print('Insurers called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('Insurers'); + + if (response['status'] == 200) { + print('getInsurers - ${response['data']}'); + setState(() { + getInsurersData = List>.from(response['data']); + print('API Data - $getInsurersData'); + + filteredInsurersData = List.from(getInsurersData); + print('originalData - $filteredInsurersData'); + }); + } else { + getInsurersData = []; + filteredInsurersData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getBroker() async { + print('getBroker called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('Broker'); + + if (response['status'] == 200) { + print('getBroker - ${response['data']}'); + setState(() { + getBrokerData = List>.from(response['data']); + print('API Data - $getBrokerData'); + + filteredBrokerData = List.from(getBrokerData); + print('originalData - $filteredBrokerData'); + }); + } else { + getBrokerData = []; + filteredBrokerData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getStaffDetailsForEnquiryAssignment(int id) async { + print('getStaffDetailsForEnquiryAssignment called By handler'); + setState(() { + isLoading = true; + }); + + try { + // final response = await apiService.fetchStaffUserList(id, role); + final response = await apiService.fetchStaffListForEnquiryAssignDropDown( + id, + roleId, + ); + + if (response['status'] == 'success') { + print('getStaffDetailsForEnquiryAssignment - ${response['data']}'); + setState(() { + getStaffDetailsDataEnqAsgn = List>.from( + response['data'], + ); + print('API Data - $getStaffDetailsDataEnqAsgn'); + + filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn); + print('originalData - $filteredStaffDataEnqAsgn'); + }); + } else { + getStaffDetailsDataEnqAsgn = []; + filteredStaffDataEnqAsgn = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getStaffList( + int managerId, + role, { + String fromDate = '', + String toDate = '', + String SelectedStatus = '', + String SelectedStaffId = '', + }) async { + print('A72 => Fns called => $managerId | $role'); + setState(() { + isLoading = true; + }); + + dynamic fromDt; + dynamic toDt; + + final fromDateVal = controllers['startDate']?.text ?? ''; + final toDateVal = controllers['endDate']?.text ?? ''; + final dateFormat = DateFormat('dd-MM-yyyy'); + + if (dashboardKey != 'fromDashboard' && + fromDateVal == '' && + toDateVal == '') { + print("ENQ LIST PAGE"); + + final today = DateTime.now(); + fromDt = dateFormat.format(today.subtract(Duration(days: 15))); + toDt = dateFormat.format(today); + + print('Enq FROM TO - $fromDt - $toDt'); + } else { + fromDt = fromDateVal; + toDt = toDateVal; + } + + try { + final response = await apiService.fetchEnquiryList( + managerId, + role, + fromDate: fromDt, + toDate: toDt, + selectedStatus: SelectedStatus != null ? SelectedStatus : '', + selectedStaffId: SelectedStaffId ?? '', + ); + + if (response['status'] == 'success') { + final data = response['data']; + final fromDate = response['from_date'] ?? ''; + final toDate = response['to_date'] ?? ''; + + print('FromDate : $fromDate'); + print('ToDate : $toDate'); + + setState(() { + controllers['startDate']?.text = fromDate; + controllers['endDate']?.text = toDate; + if (data is List) { + getStaffData = List>.from(data); + } else if (data is Map) { + getStaffData = [Map.from(data)]; + } else { + getStaffData = []; + } + originalData = getStaffData; + filteredData = List.from(originalData); + }); + } else { + getStaffData = []; + originalData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + 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); + } + + void filterData(String query) { + print("FilterDAta - $query"); + setState(() { + filteredData = getStaffData.where((item) { + // final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive"; + + return (item['agent_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['reg_no'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insurer_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['assigned_to_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insured_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['premium_amount'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['payment_mode'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (_formatDate(item['created_on']) ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (_formatDate(item['updated_on']) ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['policy_number'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['status'] ?? '-').toLowerCase().contains(query.toLowerCase()); + }).toList(); + }); + } + + final TextEditingController _searchStaffController = TextEditingController(); + + String _formatDate(String rawDate) { + try { + final dateTime = DateTime.parse(rawDate); + return DateFormat( + 'dd-MM-yyyy HH:mm', + ).format(dateTime); // 24-hour format + } catch (e) { + return rawDate; // fallback if parsing fails + } + } + + Future handleStaff( + BuildContext context, + dynamic data, + id, + regNum, + ) async { + showDialog( + context: context, + builder: (ctx) => AssignStaffDialog( + enquiryPrimaryId: id, + regNum: regNum, + userId: userId, + onSubmit: (value) { + debugPrint("New assignY: $value"); + refresh(); + }, + ), + ); + } + + Future handleProposalAccept(BuildContext context, String id) async { + final result = showDialog( + context: context, + barrierDismissible: true, // allows tapping outside to close + builder: (ctx) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + insetPadding: const EdgeInsets.all(24), + child: Container( + width: 500, + height: 300, + color: Colors.white, + // width: MediaQuery.of(context).size.width * 0.4, + // height: MediaQuery.of(context).size.height * 0.4, + // width: MediaQuery.of(context).size.width * 0.7, + // height: MediaQuery.of(context).size.height * 0.8, + child: QuotationPopUpTab(id: id, onRefresh: refrshfilterDateRange), + ), + ); + }, + ); + + print("Dialog result: $result"); + (); + } + + void handleEdit(Map item) { + print("handleEdit"); + + final id = int.tryParse(item['id'].toString()) ?? 0; + idPrimary = item['id']; + + print("handleEdit - $idPrimary"); + setState(() { + selectedId = idPrimary; + isEditingRow[id] = true; + }); + for (final field in tabHeader) { + // Only set if controller exists + if (controllers.containsKey(field)) { + controllers[field]!.text = item[field] != null + ? item[field].toString() + : ''; // fallback to empty string + } + } + + //dropdowns or IDs + selectedVehicleType = item['vehicle_type_id']; + selectedAgent = item['agent_id']; + managerId = item['manager_id']; + controllers["regNo"]?.text = item['reg_no']; + controllers["remarks"]?.text = item['remarks']; + + selectedInsurer = item['insurer_id']; + selectedStaff = item['assigned_to']; + setState(() {}); + + // setState(() { + // isEditingRow[id] = true; + // rowControllers[id] = { + // 'agent_name': TextEditingController(text: item['agent_name'] ?? ''), + // 'insurer_name': TextEditingController(text: item['insurer_name'] ?? ''), + // 'reg_no': TextEditingController(text: item['reg_no'] ?? ''), + // 'premium_amount': TextEditingController( + // text: item['premium_amount'] ?? '', + // ), + // 'payment_mode': TextEditingController(text: item['payment_mode'] ?? ''), + // }; + // }); + } + + void handleSave(dynamic id) { + print('SAVE -- $id'); + // final ids = int.tryParse(id.toString()) ?? 0; + + idPrimary = id; + + // final controllers = rowControllers[id]; + // if (controllers == null) return; + // if (!_validateRequiredFields()) return; + // setState(() { + // final index = getStaffData.indexWhere( + // (e) => e['id'].toString() == id.toString(), + // ); + // if (index != -1) { + // getStaffData[index] = { + // ...getStaffData[index], + // 'agent_name': controllers['agent_name']?.text, + // 'insurer_name': controllers['insurer_name']?.text, + // 'reg_no': controllers['reg_no']?.text, + // 'premium_amount': controllers['premium_amount']?.text, + // 'payment_mode': controllers['payment_mode']?.text, + // }; + // } + // + // if (index != -1) { + // print("EDITField - ${getStaffData[index]}"); + // getStaffData[index] = { + // ...getStaffData[index], + // 'agent_id': selectedAgent, + // "name": controllers["name"]?.text, + // "mobile": controllers["mobile"]?.text, + // "email": controllers["email"]?.text, + // "reg_no": controllers["regNo"]?.text, + // "vehicle_type_id": selectedVehicleType, + // }; + // } + + // isEditingRow[id] = false; + // rowControllers.remove(id); + // }); + handleEnqTabSave(); + } + + void handleCancel(int id) { + print('handleCancel id - $id'); + + final bool isNewRow = + int.tryParse(id.toString()) != null && + int.parse(id.toString()) > 1000000000000; + + print('isNewRow - $isNewRow'); + + setState(() { + if (isNewRow) { + // Check if this is a new unsaved row + final row = getStaffData.firstWhere( + (item) => item['id'].toString() == id.toString(), + orElse: () => {}, + ); + + // If policy_number is empty, it's a new row - remove it + if (row.isNotEmpty + // (row['policy_number'] == null || + // row['policy_number'] == '' || + // row['policy_number'] == '-') + ) { + getStaffData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + originalData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + filteredData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + rowControllers.remove(id); + } + } + + // Turn off editing mode + isEditingRow.remove(id); + + // // Clear idPrimary if it matches + // if (idPrimary.toString() == id.toString()) { + // idPrimary = null; + // } + }); + } + + //-------------------------------Assign Logics staff start ---------------------------// + + Widget buildSelectStaffMem(ctx) { + Map? selectedVehicle; + try { + selectedVehicle = filteredStaffDataEnqAsgn.firstWhere( + (item) => item['id'].toString() == selectedStaff, + ); + } catch (e) { + selectedVehicle = null; // ✅ fallback + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Assigned To', style: _tableDataTimeStyle), + + SizedBox( + height: 30, + width: MediaQuery.of(context).size.width * 0.08, + child: DropdownSearch>( + key: dropDownSelectStaffKey, + // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, + selectedItem: selectedVehicle, + items: (filter, infiniteScrollProps) { + return filteredStaffDataEnqAsgn; + }, + + itemAsString: (val) => val['name'].toString(), + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Staff Member", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.black, + width: 0.1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.black, + width: 0.1, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Staff ...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected Staff : ${val['name']}"); + print("Id: ${val['id']}"); + selectedStaffName = val['name']; + selectedStaff = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + + Widget buildInsurer(BuildContext context, {fromHeader = true}) { + Map? selectedInsurerd = filteredInsurersData.firstWhere( + (item) => item['id'].toString() == selectedInsurer, + orElse: () => {}, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Insurer', style: _tableDataTimeStyle), + Container( + height: 30, + width: fromHeader + ? MediaQuery.of(context).size.width * 0.1 + : MediaQuery.of(context).size.width * 0.08, + child: DropdownSearch>( + key: dropDownKeyInsurerEnqAsgn, + selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, + items: (filter, infiniteScrollProps) { + return filteredInsurersData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Insurer", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.black, + width: 0.1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.black, + width: 0.1, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Insurer...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + // constraints: BoxConstraints(), + ), + + onChanged: (val) { + if (val != null) { + print("Selected Insurer : ${val['name']}"); + print("Id: ${val['id']}"); + selectedInsurer = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + + Widget buildBroker(BuildContext context) { + Map? selectedBrokers = filteredBrokerData.firstWhere( + (item) => item['id'].toString() == selectedBroker, + orElse: () => {}, + ); + return SizedBox( + height: 30, + width: MediaQuery.of(context).size.width * 0.07, + child: DropdownSearch>( + key: dropDownKeyBroker, + selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null, + items: (filter, infiniteScrollProps) { + return filteredBrokerData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Broker", + ).copyWith( + filled: true, + fillColor: Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Broker...", + hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + // constraints: BoxConstraints(), + ), + + onChanged: (val) { + if (val != null) { + print("Selected Broker : ${val['name']}"); + print("Id: ${val['id']}"); + selectedBroker = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ); + } + + // -------------------------------Assign Logics staff end---------------------------// + + //---------------------- Enquiry TAB Logins Starts -------------------------------- // + + Map dataEnquiryTabDetails() { + final data = { + "agent_id": ((roleId == 'handler') || (roleId == 'manager')) + ? selectedAgent + : userId, + "name": controllers["name"]?.text, + "mobile": controllers["mobile"]?.text, + "email": controllers["email"]?.text, + "reg_no": controllers["regNo"]?.text, + "vehicle_type_id": selectedVehicleType, + "is_data_created_by_handler": roleId == 'handler' ? '1' : '0', + "is_data_created_by_manager": roleId == 'manager' ? '1' : '0', + // "insurer_id": selectedInsurer, + // "rc_file_name": "rc_doc.pdf", + // "id_proof_file_name": "id_proof.pdf", + // "previous_policy_file_name": "previous_policy.pdf", + "remarks": controllers["remarks"]?.text, + "manager_id": managerId, + "created_by": userId, + }; + + return data; + } + + Future getVehicleType() async { + print('getClaimList called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('vehicleType'); + + if (response['status'] == 200) { + print('getVehicleTypeData - ${response['data']}'); + setState(() { + getVehicleTypeData = List>.from( + response['data'], + ); + print('API Data - $getVehicleTypeData'); + + filteredVechicleData = List.from(getVehicleTypeData); + print('originalData - $filteredVechicleData'); + }); + } else { + getVehicleTypeData = []; + filteredVechicleData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getAgentList(id) async { + print('getAgentListData called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchAgentNameDropDown(id); + print('getAgentListData called response'); + print('get Agent- ${response['data']}'); + if (response['status'] == 'success') { + print('get Agent- ${response['data']}'); + setState(() { + getAgentListData = List>.from(response['data']); + print('API Data - $getAgentListData'); + + filteredAgentData = List.from(getAgentListData); + print('originalAgentData - $filteredAgentData'); + }); + } else { + getAgentListData = []; + filteredAgentData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Widget buildAgentName(BuildContext context, {fromHeader = true}) { + Map? selectedAgntName = filteredAgentData.firstWhere( + (item) => item['id'].toString() == selectedAgent, + orElse: () => {}, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Partner', style: _tableDataTimeStyle), + SizedBox( + width: MediaQuery.of(context).size.width * 0.08, + // width: fromHeader + // ? MediaQuery.of(context).size.width * 0.11 + // : MediaQuery.of(context).size.width * 0.08, + height: 30, + child: DropdownSearch>( + key: dropDownKeyAgent, + selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null, + items: (filter, infiniteScrollProps) { + return filteredAgentData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null + ? selectedItem['name'].toString() + : "", // ✅ FIXED + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Partner Name", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.black, + width: 0.1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.black, + width: 0.1, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Partner Name", + hintStyle: GoogleFonts.inter( + fontSize: 10, + color: Colors.grey, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + item['name'].toString(), + style: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + ), + Text( + item['agent_code'].toString(), + style: GoogleFonts.inter( + fontSize: 11, + color: Colors.grey, + ), + ), + ], + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected Partner : ${val['name']}"); + print("Id: ${val['id']}"); + selectedAgent = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + + Widget buildVehicleNumber(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Vehicle Number', style: _tableDataTimeStyle), + SizedBox( + width: MediaQuery.of(context).size.width * 0.08, + child: ThemedFormInlineField( + controller: controllers['regNo']!, + hintText: 'Vehicle Number', + borderColor: Colors.black, + isdense: true, + inputFormatters: [ + UpperCaseTextFormatter(), // 👈 custom formatter for uppercase + FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')), + ], + validator: (value) => Validators.requiredVechileNum(value, "regNo"), + widthNone: true, + ), + ), + ], + ); + } + + Widget buildInsuredName(BuildContext context, {fromHeader = true}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Insured', style: _tableDataTimeStyle), + SizedBox( + width: fromHeader + ? MediaQuery.of(context).size.width * 0.1 + : MediaQuery.of(context).size.width * 0.08, + child: ThemedFormInlineField( + controller: controllers['name']!, + hintText: 'Insured Name', + // errorText: fieldErrors['name'], + borderColor: Colors.black, + isdense: true, + validator: (value) => Validators.requiredField(value, "name"), + widthNone: true, + ), + ), + ], + ); + } + + Widget buildVehicleType(BuildContext context) { + // Find the matching map from your list + + Map? selectedVehicle = filteredVechicleData.firstWhere( + (item) => item['id'].toString() == selectedVehicleType, + orElse: () => {}, + ); + + return SizedBox( + width: MediaQuery.of(context).size.width * 0.07, + height: 30, + child: DropdownSearch>( + key: dropDownKey, + selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, + items: (filter, infiniteScrollProps) { + return filteredVechicleData; + }, + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + + itemAsString: (val) => val['vehicle_type'].toString(), + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null + ? selectedItem['vehicle_type'].toString() + : "", // ✅ FIXED + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Vehicle Type", + ).copyWith( + filled: true, + fillColor: Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 200), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + padding: const EdgeInsets.all(1.0), + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Vehicle Type...", + hintStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black), + labelStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), // 👈 Normal border + ), + isDense: true, // reduces height + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 8, // reduce this value for smaller height + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + child: Text( + item['vehicle_type'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected vehicle_type : ${val['vehicle_type']}"); + print("Id: ${val['id']}"); + selectedVehicleType = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ); + } + + Widget buildEmail(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Email', style: _tableDataTimeStyle), + SizedBox( + width: MediaQuery.of(context).size.width * 0.1, + child: ThemedFormInlineField( + controller: controllers['email']!, + borderColor: Colors.black, + hintText: 'Email', + isdense: true, + validator: (value) => Validators.nonReqemail(value, "email"), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')), + ], + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.16, + ), + ), + ], + ); + } + + Widget buildPhNumber(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Mobile', style: _tableDataTimeStyle), + SizedBox( + width: MediaQuery.of(context).size.width * 0.1, + child: ThemedFormInlineField( + controller: controllers['mobile']!, + borderColor: Colors.black, + hintText: 'Mobile', + isdense: true, + validator: (value) => Validators.nonReqphone(value, "phone"), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[ 0-9]')), + ], + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.26, + ), + ), + ], + ); + } + + Widget buildRemarks(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Remarks', style: _tableDataTimeStyle), + ThemedFormField( + // maxLength: 500, + enableBorderWidth: 0.1, + hintText: 'Remarks', + controller: controllers['remarks']!, + borderColor: Colors.black, + verticalPad: 10, + horizonalPad: 10, + isdense: true, + // keyboardType: TextInputType.multiline, + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.1, + ), + ], + ); + } + + Widget buildResponsiveUploadField({ + required String label, + required String? hintText, + required double? width, + required void Function(String fileName, dynamic file) onFileSelected, + bool showDownload = false, + VoidCallback? onRemove, + VoidCallback? onDownload, + }) { + final isMobile = ResponsiveLayout.isMobile(context); + + final uploadWidget = ThemedUploadField( + hintText: hintText ?? "Upload Document", + txtwidth: width, + // txtwidth: MediaQuery.of(context).size.width * 0.08, + padVertical: 6, + padHorizontal: 8, + + // backgroundColor: Color(0xffD9EBE8), + txtName: label, + isTxtBtnCase: true, + onFileSelected: onFileSelected, + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [uploadWidget], + ), + ], + ); + } + + Widget buildUploadRCDocument(BuildContext context) { + return SizedBox( + // width: MediaQuery.of(context).size.width * 0.08, + child: buildResponsiveUploadField( + label: "RC", + width: 60, + hintText: selectedRCFile, + onFileSelected: (fileName, file) { + setState(() { + docUploadedRCFile = file; + selectedRCFileName = fileName; + }); + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: docUploadedRCFile != null || rcFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedRCFile = null; + rcFileUrlFromApi = null; + selectedRCFileName = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc', + + apiId: selectedId.toString(), + + localFile: docUploadedRCFile, + fileName: 'RC', + ), + ), + ); + } + + Widget buildUploadIDDocument(BuildContext context) { + return SizedBox( + // width: MediaQuery.of(context).size.width * 0.08, + child: buildResponsiveUploadField( + label: "ID Proof", + width: 80, + hintText: selectedIdProof, + onFileSelected: (fileName, file) { + setState(() { + docUploadedIDProof = file; + // selectedFileNames = fileName; + }); + + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: + docUploadedIDProof != null || idProofFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedIDProof = null; + idProofFileUrlFromApi = null; + // selectedFileNames = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof', + + apiId: selectedId.toString(), + localFile: docUploadedIDProof, + fileName: 'Id_Proof', + ), + ), + ); + } + + Widget buildUploadPolicyDocument(BuildContext context) { + return SizedBox( + // width: MediaQuery.of(context).size.width * 0.08, + child: buildResponsiveUploadField( + width: 100, + label: "PrevPolicy", + hintText: selectedPrevPolicy, + onFileSelected: (fileName, file) { + setState(() { + docUploadedPrevPolicy = file; + // selectedFileNames = fileName; + }); + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: + docUploadedPrevPolicy != null || prevPolicyFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedPrevPolicy = null; + prevPolicyFileUrlFromApi = null; + // selectedFileNames = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy', + + apiId: selectedId.toString(), + localFile: docUploadedPrevPolicy, + fileName: 'Previous_Policy', + ), + ), + ); + } + + Widget buildDocRC(BuildContext context, selectedEnquiryId) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.16, + child: InkWell( + onTap: () { + print("Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc'; + print("Uploadpath $path"); + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Tooltip( + message: 'Download RC', + child: Text( + "RC", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget buildDocIdProof(BuildContext context, selectedEnquiryId) { + return InkWell( + onTap: () { + print("Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof'; + print("Uploadpath $path"); + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Tooltip( + message: 'Download ID Proof', + child: Text( + "ID Proof", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ); + } + + Widget buildDocPrevPolicy(BuildContext context, selectedEnquiryId) { + return InkWell( + onTap: () { + print("QD597 Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy'; + + print("Uploadpath $path"); + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Tooltip( + message: 'Download Previous Policy', + child: Text( + "Prev Policy", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ); + } + + // Update validation method to set errors + bool _validateRequiredFields() { + setState(() { + fieldErrors.clear(); // Clear previous errors + + // Validate Partner/Agent + if (selectedAgent == null || selectedAgent == '') { + fieldErrors['agent'] = 'Partner is required'; + } + + // Validate Insured Name + if (controllers['name']?.text.trim().isEmpty ?? true) { + fieldErrors['name'] = 'Insured Name is required'; + } + + // Validate Vehicle Number + if (controllers['regNo']?.text.trim().isEmpty ?? true) { + fieldErrors['regNo'] = 'Vehicle Number is required'; + } + + // Validate Vehicle Type + if (selectedVehicleType == null || selectedVehicleType == '') { + fieldErrors['vehicleType'] = 'Vehicle Type is required'; + } + + // Validate email format if provided + final email = controllers['email']?.text.trim() ?? ''; + if (email.isNotEmpty) { + final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); + if (!emailRegex.hasMatch(email)) { + fieldErrors['email'] = 'Invalid email format'; + } + } + + // Validate mobile number if provided + final mobile = controllers['mobile']?.text.trim() ?? ''; + if (mobile.isNotEmpty) { + final cleanMobile = mobile.replaceAll(' ', ''); + if (cleanMobile.length < 10) { + fieldErrors['mobile'] = 'Must be at least 10 digits'; + } + } + }); + + return true; + + return fieldErrors.isEmpty; + } + + Future handleEnqTabSave() async { + if (!_validateRequiredFields()) return; + + // setState(() { + // isSaving = true; // start saving + // }); + + final dataSet = dataDetails(); + + try { + await createUserData(dataSet); // API call + + // ToastHelper.showInfoToast(context, "Enquiry saved successfully"); + } catch (e) { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('Error'), + content: Text(e.toString()), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + } finally { + // setState(() => isSaving = false); // re-enable button after save + } + } + + Future attachFiles(http.MultipartRequest request) async { + Future addFileOrKeepName( + PlatformFile? file, + String? apiFileName, + String fieldName, + ) async { + if (file != null) { + // User uploaded a new file → send as multipart + if (file.bytes != null) { + request.files.add( + http.MultipartFile.fromBytes( + fieldName, + file.bytes!, + filename: file.name, + ), + ); + } else if (file.path != null) { + request.files.add( + await http.MultipartFile.fromPath( + fieldName, + file.path!, + filename: file.name, + ), + ); + } + print("📎 Attached new file → $fieldName"); + } else if (apiFileName != null && apiFileName.isNotEmpty) { + // No new upload → tell backend to keep old file + request.fields[fieldName] = apiFileName; + print("🔗 Kept old file → $fieldName = $apiFileName"); + } else { + // Nothing at all + request.fields[fieldName] = ""; + } + } + + await addFileOrKeepName( + docUploadedRCFile, + rcFileUrlFromApi, + 'rc_file_name', + ); + await addFileOrKeepName( + docUploadedIDProof, + idProofFileUrlFromApi, + 'id_proof_file_name', + ); + await addFileOrKeepName( + docUploadedPrevPolicy, + prevPolicyFileUrlFromApi, + 'previous_policy_file_name', + ); + } + + List _buildPopupDownload(BuildContext context, selectId) { + bool isMobile = ResponsiveLayout.isMobile(context); + return [ + // editing + // ? + Container( + // color: Colors.red.shade50, + padding: EdgeInsets.only(top: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + + children: [ + buildUploadRCDocument(context), + SizedBox(width: 3), + buildUploadIDDocument(context), + SizedBox(width: 3), + buildUploadPolicyDocument(context), + ], + ), + ), + // : Row( + // children: [ + // buildDocRC(context, selectId), + // SizedBox(width: 3), + // buildDocIdProof(context, selectId), + // SizedBox(width: 3), + // buildDocPrevPolicy(context, selectId), + // ], + // ), + ]; + } + + Widget _buildSubRow(context, selectId, id) { + return Wrap( + spacing: 12, + runSpacing: 12, + children: [ + buildAgentName(context, fromHeader: false), + buildSelectStaffMem(context), + + buildInsurer(context), + buildInsuredName(context), + buildVehicleNumber(context), + + buildEmail(context), + buildPhNumber(context), + buildRemarks(context), + + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Builder( + builder: (buttonContext) => Material( + color: Color(0xFFFFFCF2), + child: InkWell( + hoverColor: const Color(0xFFFFFCF2), + // hoverColor: const Color(0xFFEAF6F4), + onTap: () async { + // ✅ Get overlay and button position RELATIVE to InkWell + final RenderBox button = + buttonContext.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(buttonContext).context.findRenderObject() + as RenderBox; + + final Offset position = button.localToGlobal( + Offset.zero, + ancestor: overlay, + ); + + await showMenu( + context: buttonContext, + position: RelativeRect.fromLTRB( + position.dx, + position.dy + button.size.height, + overlay.size.width, + 0, + ), + items: [ + PopupMenuItem( + child: Column( + mainAxisSize: MainAxisSize.min, + children: _buildPopupDownload( + buttonContext, + selectId, + ), + ), + ), + ], + // color: Colors.white, + color: Color(0xFFFFFCF2), + ); + }, + child: Tooltip( + message: 'Click To Upload Documents', + + waitDuration: const Duration(milliseconds: 500), + showDuration: const Duration(seconds: 2), + child: Icon( + Icons.upload_file, + // editing ? Icons.upload_file : Icons.download_for_offline, + // color: editing ? Colors.blue : Colors.green, + size: 28, + ), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: IconButton( + tooltip: 'Save', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 24, minHeight: 24), + icon: const Icon( + Icons.check, + color: Colors.green, + size: 28, // smaller icon + ), + splashRadius: 14, // ripple radius + onPressed: () => { + setState(() { + rowExpanded[id] = !(rowExpanded[id] ?? false); + }), + handleSave(selectId), + }, + ), + ), + ], + ); + } + + Widget _buildSubRow2(BuildContext context, String selectId) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + Row( + children: [ + Expanded(child: buildAgentName(context)), + Expanded(child: buildVehicleNumber(context)), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded(child: buildEmail(context)), + Expanded(child: buildPhNumber(context)), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded(child: buildRemarks(context)), + SizedBox( + width: 50, + child: Builder( + builder: (buttonContext) => Material( + color: Color(0xFFFFFCF2), + child: InkWell( + hoverColor: const Color(0xFFFFFCF2), + // hoverColor: const Color(0xFFEAF6F4), + onTap: () async { + // ✅ Get overlay and button position RELATIVE to InkWell + final RenderBox button = + buttonContext.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(buttonContext).context.findRenderObject() + as RenderBox; + + final Offset position = button.localToGlobal( + Offset.zero, + ancestor: overlay, + ); + + await showMenu( + context: buttonContext, + position: RelativeRect.fromLTRB( + position.dx, + position.dy + button.size.height, + overlay.size.width, + 0, + ), + items: [ + PopupMenuItem( + child: Column( + mainAxisSize: MainAxisSize.min, + children: _buildPopupDownload( + buttonContext, + selectId, + ), + ), + ), + ], + // color: Colors.white, + color: Color(0xFFFFFCF2), + ); + }, + child: Tooltip( + message: 'Click To Upload Documents', + + waitDuration: const Duration(milliseconds: 500), + showDuration: const Duration(seconds: 2), + child: Icon( + Icons.upload_file, + // editing ? Icons.upload_file : Icons.download_for_offline, + // color: editing ? Colors.blue : Colors.green, + size: 22, + ), + ), + ), + ), + ), + ), + ], + ), + ], + ); + } + + Future createUserData(Map userData) async { + final bool isNewRow = + idPrimary != null && + int.tryParse(idPrimary.toString()) != null && + int.parse(idPrimary.toString()) > 1000000000000; + + final bool isUpdating = + idPrimary != null && idPrimary != 'null' && !isNewRow; + + print('isNewRow: $isNewRow, isUpdating: $isUpdating'); + + // final bool isUpdating = idPrimary != 'null'; + final id = idPrimary; + print('UPDId- $id'); + + // final bool isUpdating = false; + final uri = Uri.parse( + isUpdating + ? '${Env.apiUrl}enquiry/updateEnquiry' + : '${Env.apiUrl}enquiry/createEnquiry', + ); + if (_token == null) { + throw Exception('Token not found. Please log in.'); + } + + // Use MultipartRequest (POST only) + final request = http.MultipartRequest('POST', uri); + request.headers['Authorization'] = 'Bearer $_token'; + request.headers['app-signature'] = Env.App_Signature; + + // If updating, spoof the method Laravel-style + if (isUpdating) { + request.fields['id'] = id!; + request.fields['updated_by'] = userId!.toString(); + } else { + request.fields['created_by'] = userId!.toString(); + } + + print("USerDAta - $userData"); + + // userData.forEach((key, value) { + // request.fields[key] = value.toString(); + // print("✅ Encoded travel_details2: ${request.fields[key]}"); + // }); + await attachFiles(request); + userData.forEach((key, value) { + if (key != 'rc_file_name' && key != 'id_proof_file_name') { + request.fields[key] = value.toString(); + print("✅ Encoded $key: ${request.fields[key]}"); + } else { + print('Something Missing..'); + } + }); + + // attach files + + // request.fields['agent_id'] = selectedId.toString(); + + print(" Sending request with fields: ${request.fields}"); + + try { + final streamedResponse = await request.send(); + final response = await http.Response.fromStream(streamedResponse); + print("Response status: ${response.statusCode}"); + print("Response body: ${response.body}"); + + if (response.statusCode == 200 || response.statusCode == 201) { + // dispose(); + print("✅ Enquiry submitted successfully!"); + ToastHelper.showSuccessToast(context, 'Saved Enquiry'); + print("Response: ${response.body}"); + // Clear the temporary ID + idPrimary = null; + refrshfilterDateRange(); + // getStaffList(managerId, roleId); + // setState(() => isSaving = false); + } else if (response.statusCode == 403) { + await apiService.clearLocalStorageAndRedirect(); + } else { + print("❌ Submission failed. Status: ${response.statusCode}"); + print("Body: ${response.body}"); + + // setState(() => isSaving = false); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Enquiry Creation Failed"), + content: Text( + "There was a problem in creating enquiry. Please try again.", + ), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + } catch (e) { + print("🔥 Error submitting user: $e"); + } + } + + //---------------------- Enquiry TAB Logins End -------------------------------- // + @override + Widget build(BuildContext context) { + return MainLayout( + title: "Enquiries", + body: Container( + // color: Colors.yellow.shade50, + // color: Color(0xFFFCFCFC), + width: MediaQuery.of(context).size.width, + padding: EdgeInsets.all(3.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (roleId != 'staff') ...[ + RaiseEnqForm( + key: raiseEnqKey, + onSubmit: (dataSet) async { + try { + await createUserData(dataSet); // <-- API call here + print("Enquiry saved successfully"); + // RESET FORM + raiseEnqKey.currentState?.resetFormFields(); + } catch (e) { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('Error'), + content: Text(e.toString()), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + } + }, + ), + ], + + // SizedBox(height: 5), + ResponsiveLayout.isMobile(context) + ? Container( + height: MediaQuery.of(context).size.height * 0.69, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(2), + child: _buildContent(context), + ), + ), + ) + : Expanded( + child: Container( + width: MediaQuery.of(context).size.width, + // padding: EdgeInsets.symmetric( + // horizontal: 8.0, + // vertical: 2.0, + // ), + child: _buildContent(context), + ), + ), + + 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; + }); + }, + ), + ), + ], + ), + ), + ); + } + + Future buildStatusActions(BuildContext context, status, id) async { + print("Actionsstatus - $status -$id"); + dynamic val; + if (status == 'Awaiting Proposal' || status == 'Proposal Created') { + val = ''; + } else { + val = 'Policy'; + } + + final result = await showDialog( + context: context, + barrierDismissible: + false, // optional - prevents closing by tapping outside + builder: (context) => TabEnquiryStaffList(showKey: val), + ); + + // ✅ Code here runs *after* the dialog is closed + print("Dialog closed"); + print("Dialog result: $result"); + refrshfilterDateRange(); + } + + Widget _buildContent(BuildContext context) { + final isDesktop = !ResponsiveLayout.isMobile(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + // height: 40, + // color: Colors.pink, + child: Row( + 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), + onPressed: () async { + context.go(AppRoutes.dashboard); + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('dashboardKeyProvider'); + await prefs.remove('dashboardStatusProvider'); + await prefs.remove('dashboardStaffIdProvider'); + }, + splashRadius: 18, + padding: const EdgeInsets.all(4), + hoverColor: Colors.black12, + constraints: const BoxConstraints(), + ), + ), + const SizedBox(width: 5), // spacing between icon and text + Text( + "Enquiries", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + Spacer(), + + IconButton( + icon: Icon( + _showFilterRow + ? Icons.close_fullscreen + : Icons.filter_alt_outlined, + ), + iconSize: 20, + tooltip: _showFilterRow ? 'Hide Filters' : 'Show Filters', + onPressed: () { + setState(() { + _showFilterRow = !_showFilterRow; + }); + }, + ), + if (_showFilterRow) ...[ + DateFilterRow( + key: ValueKey(SelectedStatus ?? ''), + role: roleId, + id: userId, + selectedStatusVal: SelectedStatus, + onFilterStaff: (val) { + print('Selected Filterd STAFF Id - $val'); + SelectedStaffId = val; + }, + selectedStaffId: SelectedStaffId, + startController: controllers['startDate']!, + endController: controllers['endDate']!, + onStatusChanged: (val) { + SelectedStatus = val; // update parent + }, + formKey: _formKey, + isMobile: ResponsiveLayout.isMobile(context), + onFilter: () { + // call your filter logic + filterDateRange(); + }, + onRefresh: () { + // call your refresh logic + refrshfilterDateRange(); + }, + ), + ], + + // ResponsiveLayout.isMobile(context) + // ? Spacer() + // : + // SizedBox(width: 10), + ThemedSearchField( + hintText: 'Search', + // backgroundColor: Color(0xFFF6F8F8), + backgroundColor: Colors.white, + onChanged: filterData, + txtHeight: 35, + controller: _searchStaffController, + txtwidth: ResponsiveLayout.isMobile(context) + ? MediaQuery.of(context).size.width * 0.7 + : MediaQuery.of(context).size.width * 0.13, + ), + SizedBox(width: 10), + ExportBtn( + sheetName: "Enquiry", + fileName: "Enquiry_list", + data: filteredData, + txt: !ResponsiveLayout.isMobile(context) ? true : false, + displayHeaders: [ + "Received Date & Time", + "Partner", + "Assigned To", + "Insurer", + "Vehicle.No.", + "Insured Name", + "Assigned Date & Time", + "Premium", + "Payment Mode", + "Policy Number", + "Status", + ], + keys: [ + "created_on", + "agent_name", + "assigned_to_name", + "insurer_name", + "reg_no", + "insured_name", + "updated_on", + "premium_amount", + "payment_mode", + "policy_number", + "status", + ], + ), + // if (roleId != 'staff') ...[ + // SizedBox(width: 10), + // GestureDetector( + // onTap: () async { + // // final prefs = await SharedPreferences.getInstance(); + // // + // // await prefs.remove('enqAgentDataId'); + // // ref.read(enquiryIdProvider.notifier).state = null; + // // context.go(AppRoutes.tabEnquiry); + // // print('Export'); + // + // // Generate a unique ID for the new row + // final newId = DateTime.now().millisecondsSinceEpoch; + // + // // Clear all existing form controllers + // for (String field in tabHeader) { + // controllers[field]?.clear(); + // } + // + // // Reset dropdown selections + // selectedVehicleType = null; + // selectedAgent = null; + // selectedInsurer = null; + // + // // Reset file uploads + // docUploadedRCFile = null; + // docUploadedIDProof = null; + // docUploadedPrevPolicy = null; + // selectedRCFile = null; + // selectedIdProof = null; + // selectedPrevPolicy = null; + // + // setState(() { + // // Create a new empty row data + // final newRow = { + // 'id': newId + // .toString(), // Keep as String for consistency + // 'created_on': '', + // 'agent_name': '', + // 'agent_id': '', + // 'assigned_to_name': '', + // 'insurer_name': '', + // 'insured_name': '', + // 'reg_no': '', + // 'vehicle_type': '', + // 'vehicle_type_id': '', + // 'email': '', + // 'mobile': '', + // 'remarks': '', + // 'updated_on': '', + // 'premium_amount': '', + // 'payment_mode': '', + // 'policy_number': '', + // 'status': '', + // }; + // + // // Insert at the beginning of ALL lists + // getStaffData.insert(0, newRow); + // originalData.insert(0, newRow); + // filteredData.insert(0, newRow); + // + // // Set to editing mode immediately + // isEditingRow[newId] = true; + // + // // Store the ID as string + // idPrimary = newId.toString(); + // selectedId = newId.toString(); + // + // // Reset to first page to see the new row + // currentPage = 1; + // }); + // }, + // child: Container( + // padding: EdgeInsets.all(8.0), + // decoration: BoxDecoration( + // color: Color(0xFF425B5B), + // borderRadius: BorderRadius.circular(8.0), + // ), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Tooltip( + // message: 'Raise Enquiry', + // child: Icon(Icons.add, color: Colors.white, size: 15), + // ), + // ], + // ), + // ), + // ), + // ], + ], + ), + ), + + // SizedBox(height: 10), + + // Container( + // decoration: BoxDecoration( + // color: const Color(0xFFEDF6F5), + // borderRadius: BorderRadius.circular(6), + // ), + // padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + // child: Row( + // children: const [ + // Expanded(flex: 1, child: Text('S.No', style: _headerStyle)), + // Expanded( + // flex: 2, + // child: Text('Received Date', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Partner *', style: _headerStyle)), + // Expanded( + // flex: 2, + // child: Text('Assigned To *', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Broker *', style: _headerStyle)), + // Expanded(flex: 3, child: Text('Insurer *', style: _headerStyle)), + // Expanded( + // flex: 3, + // child: Text('Insured Name *', style: _headerStyle), + // ), + // Expanded( + // flex: 2, + // child: Text('Vehicle No *', style: _headerStyle), + // ), + // Expanded( + // flex: 2, + // child: Text('Vehicle Type *', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Email', style: _headerStyle)), + // Expanded(flex: 2, child: Text('Mobile', style: _headerStyle)), + // Expanded(flex: 2, child: Text('Documents', style: _headerStyle)), + // Expanded(flex: 2, child: Text('Remarks', style: _headerStyle)), + // Expanded( + // flex: 2, + // child: Text('Assigned Date', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Premium', style: _headerStyle)), + // Expanded( + // flex: 2, + // child: Text('Payment Mode', style: _headerStyle), + // ), + // Expanded( + // flex: 2, + // child: Text('Policy Number', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Status', style: _headerStyle)), + // Expanded(flex: 1, child: Text('Action', style: _headerStyle)), + // ], + // ), + // ), + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 8, + spreadRadius: 1, + offset: const Offset(0, 2), // soft drop shadow + ), + ], + ), + // color: Colors.white, + + // color: Color(0xFFE8F5F3), + // color: Color(0xFFE8F5F3), + child: _buildTableHeader(isDesktop), + ), + ResponsiveLayout.isMobile(context) + ? _buildDataTable(context) + : Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.grey, width: 0.1), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 8, + spreadRadius: 1, + offset: const Offset(0, 2), // soft drop shadow + ), + ], + ), + child: _buildDataTable(context), + ), + ), + ], + ); + } + + // Add this to your EnquiryStaffState class + + // 1. Add group expansion state tracking + Map groupExpanded = { + 'Awaiting Proposal': true, + 'Proposal Created': true, + 'Proposal Accepted': true, + 'Proposal Rejected': true, + 'Policy Created': true, + }; + + // 2. Add method to group enquiries by status + Map>> get _groupedEnquiries { + final grouped = >>{}; + + // Initialize all status groups + grouped['Awaiting Proposal'] = []; + grouped['Proposal Created'] = []; + grouped['Proposal Accepted'] = []; + grouped['Proposal Rejected'] = []; + grouped['Policy Created'] = []; + + // Group the filtered/paginated data by status + for (var item in _paginatedData) { + final status = item['status']?.toString() ?? 'Unknown'; + if (grouped.containsKey(status)) { + grouped[status]!.add(item); + } + } + + return grouped; + } + + // 3. Add method to get status icon and color + Map _getStatusConfig(String status) { + switch (status) { + case 'Awaiting Proposal': + return {'icon': '🕐', 'color': Color(0xFFfff3cd)}; + case 'Proposal Created': + return {'icon': '📝', 'color': Color(0xFFd1ecf1)}; + case 'Proposal Accepted': + return {'icon': '✅', 'color': Color(0xFFd4edda)}; + case 'Proposal Rejected': + return {'icon': '❌', 'color': Color(0xFFf8d7da)}; + case 'Policy Created': + return {'icon': '📋', 'color': Color(0xFFcfe2ff)}; + default: + return {'icon': '📄', 'color': Color(0xFFe9ecef)}; + } + } + + // 4. Build group header widget + Widget _buildGroupHeader(String status, int count) { + final config = _getStatusConfig(status); + final isExpanded = groupExpanded[status] ?? true; + + return InkWell( + onTap: () { + setState(() { + groupExpanded[status] = !isExpanded; + }); + }, + child: Container( + decoration: BoxDecoration( + // color: Colors.grey.shade50, + // color: Color(0xFFF0F7F6), + color: Colors.grey.shade100, + border: Border.all(color: Colors.grey.shade200, width: 1.6), + // border: Border( + // bottom: BorderSide( + // width: 0.1, + // color: Colors.grey.shade50, + // // color: Color(0xffE0E7E6), + // ), + // ), + ), + + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Row( + children: [ + Icon( + isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, + size: 20, + ), + SizedBox(width: 8), + Text( + // '${config['icon']} $status', + '$status', + style: GoogleFonts.inter( + fontSize: 13, + color: Colors.black, + // color: Colors.orange, + fontWeight: FontWeight.w800, + ), + ), + Spacer(), + // SizedBox(width: 8), + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration( + color: Color(0xFFe3f2fd), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'COUNTS : $count', + style: GoogleFonts.inter( + fontSize: 12, + color: Color(0xFF1976d2), + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildDataTable(BuildContext context) { + if (filteredData.isEmpty) { + return const SizedBox( + height: 50, + child: Center(child: Text('No available data')), + ); + } + + final isDesktop = !ResponsiveLayout.isMobile(context); + final groupedData = _groupedEnquiries; + + return LayoutBuilder( + builder: (context, constraints) { + double minWidth = constraints.maxWidth < 1300 + ? 1300 + : constraints.maxWidth; + + return ScrollConfiguration( + behavior: const MaterialScrollBehavior().copyWith( + dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch}, + ), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: minWidth, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisSize: MainAxisSize.min, + children: [ + // Build DataTable for each group + ...groupedData.entries.map((entry) { + final status = entry.key; + final items = entry.value; + final isExpanded = groupExpanded[status] ?? true; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Group Header + Container( + width: double.infinity, + child: _buildGroupHeader(status, items.length), + ), + + // Group DataTable (if expanded and has items) + // Table rows with expandable content + if (isExpanded && items.isNotEmpty) + ...items.asMap().entries.map((itemEntry) { + final index = itemEntry.key; + final item = itemEntry.value; + final startIndex = + (currentPage - 1) * itemsPerPage; + final sno = startIndex + index + 1; + final id = + int.tryParse(item['id'].toString()) ?? 0; + final selectId = item['id'].toString() ?? '0'; + + // ⭐ THIS IS THE KEY - Column with DataTable + Container + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Single-row DataTable + Container( + width: double.infinity, + + child: DataTable( + headingRowHeight: 0, + dividerThickness: 0.3, + columnSpacing: isDesktop ? 20.0 : 16.0, + dataRowMinHeight: 40, + // border: TableBorder( + // bottom: BorderSide( + // width: 0.2, + // color: Colors.grey.shade50, + // // color: Color(0xffE0E7E6), + // ), + // ), + columns: _buildDataColumns(), + rows: [ + _buildDataRow(item, sno), + ], // Single row + ), + ), + if (rowExpanded[id] ?? false) ...[ + Container( + width: double.infinity, + // height: 60, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(25), + topRight: Radius.circular(25), + ), + color: Color(0xFFFFFCF2), + ), + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 5.0, + ), + child: _buildSubRow( + context, + selectId, + id, + ), + ), + ], + ], + ); + }).toList(), + + // Container(color: Colors.green, child: Text('Sub Row')), + // if (isExpanded && items.isNotEmpty) + // Container( + // width: double.infinity, + // child: DataTable( + // headingRowHeight: + // 0, // Hide header for groups (shown once at top) + // dividerThickness: 0.5, + // columnSpacing: isDesktop ? 20.0 : 16.0, + // border: TableBorder( + // horizontalInside: BorderSide( + // width: 0.5, + // color: Colors.grey.shade200, + // ), + // ), + // columns: _buildDataColumns(), + // rows: items.asMap().entries.map((itemEntry) { + // final index = itemEntry.key; + // final item = itemEntry.value; + // final startIndex = + // (currentPage - 1) * itemsPerPage; + // final sno = startIndex + index + 1; + // return _buildDataRow(item, sno); + // }).toList(), + // ), + // ), + + // Empty state + if (isExpanded && items.isEmpty) + Padding( + padding: EdgeInsets.all(20), + child: Center( + child: Text( + 'No records found', + style: GoogleFonts.inter( + fontSize: 12, + color: Colors.grey, + fontStyle: FontStyle.italic, + ), + ), + ), + ), + ], + ); + }).toList(), + ], + ), + ), + ), + ), + ); + }, + ); + } + + DataRow _buildDataRow(Map item, int sno) { + final id = int.tryParse(item['id'].toString()) ?? 0; + final selectId = item['id'].toString() ?? '0'; + + final bool isNewRow = + int.tryParse(id.toString()) != null && + int.parse(id.toString()) > 1000000000000; + + print('idPrimary - $idPrimary'); + print('isNewRow - $isNewRow'); + final editing = isEditingRow[id] ?? false; + final controllers = rowControllers[id]; + + return DataRow( + cells: [ + // S.No. + // DataCell(Text('$sno', style: _dataBold)), + + // Created On + DataCell( + SizedBox( + width: 80, + child: Row( + children: [ + // Text(item['created_on'], style: _dataBold), + Text( + formatDateTimeForTable(item['created_on'], newLine: true), + style: _dataBold, + ), + ], + ), + ), + ), + + // Agent Name + DataCell( + // editing + // ? buildSelectStaffMem(context) + // : + SizedBox( + width: 80, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(item['agent_code'] ?? '-', style: _dataBold), + Text( + item['agent_name'] != null + ? _truncateText(item['agent_name'], 10) + : '-', + style: _tableDataTimeStyle, + ), + ], + ), + ), + ), + + // Assigned To + DataCell( + // editing + // ? buildAgentName(context) + // : + SizedBox( + width: 90, + child: Text( + item['assigned_to_name'] != null + ? _truncateText(item['assigned_to_name'], 10) + : '-', + style: _dataBold, + ), + ), + ), + + // DataCell( + // editing + // ? buildBroker(context) + // : Text(item['broker_name'] ?? '-', style: _dataBold), + // ), + + // Insurer Name + DataCell( + // editing + // ? buildInsurer(context) + // : + SizedBox( + width: 80, + child: Text(item['insurer_short_name'] ?? '-', style: _dataBold), + ), + ), + + DataCell( + // editing + // ? buildInsuredName(context) + // : + SizedBox( + width: 90, + child: Tooltip( + message: item['insured_name'], + child: Text( + item['insured_name'] != null + ? _truncateText(item['assigned_to_name'], 15) + : '-', + style: _dataBold, + ), + ), + ), + ), + // Vehicle No + DataCell( + // editing + // ? buildVehicleNumber(context) + // : + SizedBox( + width: 80, + child: Text(item['reg_no'] ?? '-', style: _dataBold), + ), + ), + + DataCell( + SizedBox( + width: 40.0, + child: Row( + children: [ + Tooltip( + message: + 'Email : ${item["email"]}\n' + 'Mobile : ${item["mobile"]}\n' + 'Remarks : ${item["remarks"]}', + decoration: BoxDecoration( + color: Colors.white, // background color + borderRadius: BorderRadius.circular(6), + border: Border.all(color: Colors.grey, width: 1), + ), + textStyle: GoogleFonts.inter( + color: Colors.black, + fontSize: 12, + ), + + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, // reduce tooltip height + ), + margin: const EdgeInsets.only( + left: 10, + ), // tooltip distance from widget + waitDuration: Duration(milliseconds: 500), + showDuration: Duration(seconds: 2), + child: Icon( + Icons.info_outline_rounded, + size: 15, + color: Colors.blue, + ), + ), + + if (roleId != 'staff') ...[ + IconButton( + padding: EdgeInsets.zero, // remove outer padding + constraints: BoxConstraints( + // reduce clickable area + minWidth: 24, + minHeight: 24, + ), + icon: Icon( + rowExpanded[id] == true + ? Icons.edit_off_sharp + : Icons.edit, + color: Colors.blue, + size: 15, + ), + + tooltip: "Edit", + onPressed: () { + setState(() { + rowExpanded[id] = !(rowExpanded[id] ?? false); + }); + handleEdit(item); + }, + ), + ], + ], + ), + ), + ), + + // Assigned Date (formatted) + DataCell( + SizedBox( + width: 80, + child: Text( + // item['updated_on'] ?? '-', + formatDateTimeForTable(item['updated_on'], newLine: true), + style: _dataBold, + softWrap: true, + maxLines: 3, + ), + ), + ), + + // Premium + DataCell( + // ((item['premium_amount'] != null) && + // (item['premium_amount'] != '') && + // ((item['status'] != 'Awaiting Proposal') || + // (item['status'] == 'Proposal Created') || + // (item['status'] == 'Proposal Rejected'))) + (item['status'] == 'Proposal Created' || + item['status'] == 'Proposal Rejected') + ? SizedBox( + width: 80, + child: InkWell( + onTap: () { + // handle approval logic + + handleProposalAccept(context, item['id']); + }, + child: Text( + 'Click To\nApprove', + style: GoogleFonts.inter( + color: Colors.green, + fontSize: 11, + fontWeight: FontWeight.w700, + ), + ), + ), + ) + : SizedBox( + width: 80, + child: Text( + item['premium_amount']?.toString() ?? '-', + style: _dataBold, + ), + ), + ), + + // Payment Mode + DataCell( + SizedBox( + width: 80, + child: Text(item['payment_mode'] ?? '-', style: _dataBold), + ), + ), + + // Policy Number + DataCell( + SizedBox( + width: 90, + child: Text( + item['policy_number'] ?? '-', + style: _dataBold, + softWrap: true, + maxLines: 3, + ), + ), + ), + + // Status + // DataCell(Text(item['status'] ?? '-', style: _dataBold)), + DataCell( + SizedBox( + width: 80, + child: Material( + color: Colors.white, + child: InkWell( + hoverColor: Color(0xFFEAF6F4), + onTap: () async { + var status = item['status']; + var enqId = item['id']; + + final prefs = await SharedPreferences.getInstance(); + + // ✅ Remove old value (if any) + await prefs.remove('enqStaffDataId'); + + // ✅ Save the new id + await prefs.setString('enqStaffDataId', enqId.toString()); + ref.read(quotationStaffIdProvider.notifier).state = enqId; + buildStatusActions(context, status, enqId); + }, + child: Tooltip( + message: 'Click to View or Process Enquiry', // + waitDuration: const Duration(milliseconds: 500), // optional + showDuration: const Duration(seconds: 2), // optional + child: Text( + item['status'] ?? '-', + style: _dataBold, + overflow: TextOverflow.clip, // + ), + ), + // child: Text(item['status'] ?? '-', style: _dataBold), + ), + ), + ), + ), + ], + ); + } + + // Add this helper method to generate DataColumns + List _buildDataColumns() { + return [ + const DataColumn(label: Text('Received Date', style: _headerStyle)), + const DataColumn(label: Text('Partner *', style: _headerStyle)), + const DataColumn(label: Text('Assigned To *', style: _headerStyle)), + // const DataColumn(label: Text('Broker *', style: _headerStyle)), + const DataColumn(label: Text('Insurer *', style: _headerStyle)), + const DataColumn(label: Text('Insured Name *', style: _headerStyle)), + const DataColumn(label: Text('Vehicle No *', style: _headerStyle)), + // const DataColumn(label: Text('Vehicle Type *', style: _headerStyle)), + // const DataColumn(label: Text('Email', style: _headerStyle)), + // const DataColumn(label: Text('Mobile', style: _headerStyle)), + // const DataColumn(label: Text('Documents', style: _headerStyle)), + // const DataColumn(label: Text('Remarks', style: _headerStyle)), + const DataColumn(label: Text('Action', style: _headerStyle)), + const DataColumn(label: Text('Assigned Date', style: _headerStyle)), + const DataColumn(label: Text('Premium', style: _headerStyle)), + const DataColumn(label: Text('Payment Mode', style: _headerStyle)), + const DataColumn(label: Text('Policy Number', style: _headerStyle)), + const DataColumn(label: Text('Status', style: _headerStyle)), + ]; + } + + // 6. Build table header + Widget _buildTableHeader(bool isDesktop) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + _buildHeaderCell('Received Date', 100), + _buildHeaderCell('Partner *', 100), + _buildHeaderCell('Assigned To *', 110), + // _buildHeaderCell('Broker *', 100), + _buildHeaderCell('Insurer *', 90), + _buildHeaderCell('Insured Name *', 120), + _buildHeaderCell('Vehicle No *', 100), + // _buildHeaderCell('Vehicle Type *', 110), + // _buildHeaderCell('Email', 150), + // _buildHeaderCell('Mobile', 100), + // _buildHeaderCell('Documents', 100), + // _buildHeaderCell('Remarks', 120), + _buildHeaderCell('Action', 60), + _buildHeaderCell('Assigned Date', 100), + _buildHeaderCell('Premium', 100), + _buildHeaderCell('Payment Mode', 100), + _buildHeaderCell('Policy Number', 120), + _buildHeaderCell('Status', 100), + ], + ), + ); + } + + Widget _buildHeaderCell(String label, double width) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 4), + child: SizedBox( + width: width, + child: Text( + label, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ), + ); + } + + static final _dataBold = TextStyle( + fontSize: 12, + + fontWeight: FontWeight.w500, + color: Colors.black, + // color: Color(0xFF000000), + ); + final _tableDataTimeStyle = GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w400, + ); + static final _dataBoldsmall = TextStyle( + fontSize: 12, + + fontWeight: FontWeight.w400, + color: Color(0xFF000000), + ); + + static final _dataSub = TextStyle( + fontSize: 10, + fontWeight: FontWeight.w300, + color: Color(0xFF585757), + ); + + static const _headerStyle = TextStyle( + color: Colors.black, + fontWeight: FontWeight.w500, + ); + static const _cardheaderStyle = TextStyle( + color: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 12, + ); + + static final TextStyle _textStyle = TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + ); + + static const _cardBodyStyle = TextStyle( + color: Color(0xFF545454), + fontWeight: FontWeight.w400, + fontSize: 12, + ); + + String _truncateText(String text, int limit) { + if (text.length <= limit) return text; + return '${text.substring(0, limit)}...'; + } + + String _wrapText(String text, int chunkSize) { + if (text.isEmpty) return '-'; + final buffer = StringBuffer(); + for (int i = 0; i < text.length; i += chunkSize) { + int end = (i + chunkSize < text.length) ? i + chunkSize : text.length; + buffer.writeln(text.substring(i, end)); // adds '\n' after each chunk + } + return buffer.toString().trim(); + } + + String formatDateTimeForTable(String? input, {bool newLine = false}) { + if (input == null || input.trim().isEmpty) return '-'; + + final s = input.trim(); + DateTime dt; + + try { + // Case 1: ISO-like (yyyy-MM-dd ...), DateTime.parse will handle + if (RegExp(r'^\d{4}-\d{2}-\d{2}').hasMatch(s)) { + dt = DateTime.parse(s); + } + // Case 2: day-month-year at start (e.g. 13-11-2025 07:28 AM or 13-11-2025 07:28) + else if (RegExp(r'^\d{2}-\d{2}-\d{4}').hasMatch(s)) { + // Try with am/pm (hh:mm a) + if (RegExp(r'\b(am|pm)\b', caseSensitive: false).hasMatch(s)) { + dt = DateFormat('dd-MM-yyyy hh:mm a').parse(s); + } + // Try 24-hour time "dd-MM-yyyy HH:mm" or just date "dd-MM-yyyy" + else if (RegExp(r'^\d{2}-\d{2}-\d{4}\s+\d{1,2}:\d{2}').hasMatch(s)) { + dt = DateFormat('dd-MM-yyyy HH:mm').parse(s); + } else { + dt = DateFormat('dd-MM-yyyy').parse(s); + } + } + // Case 3: fallback — try parse with DateTime.parse (may throw) + else { + dt = DateTime.parse(s); + } + + final dateStr = DateFormat('dd-MM-yyyy').format(dt); + final timeStr = DateFormat('hh:mm a').format(dt); // e.g. 07:28 AM + + return newLine ? '$dateStr\n$timeStr' : '$dateStr | $timeStr'; + } catch (e) { + // If parsing fails, return original string as fallback (or '-' if empty) + return s.isNotEmpty ? s : '-'; + } + } +} diff --git a/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart index f25fab0..0319c81 100644 --- a/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart +++ b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart @@ -1,3 +1,4 @@ +import 'dart:ui'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; @@ -8,7 +9,8 @@ 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/handler/quotationPopUp.dart'; +import 'package:nhance_partner/presentation/screens/staff/Enquiry/quotationPopUpAccptReject.dart'; +import 'package:nhance_partner/presentation/screens/staff/Enquiry/raise_enq_form.dart'; import 'package:nhance_partner/presentation/screens/staff/Enquiry/tabs/tab.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -58,6 +60,10 @@ class EnquiryStaffState extends ConsumerState { final _formKey = GlobalKey(); Map fieldErrors = {}; + bool _showEditDrawer = false; + + final raiseEnqKey = GlobalKey(); + // List> dataVal = []; List> getStaffData = []; List> originalData = []; @@ -66,6 +72,7 @@ class EnquiryStaffState extends ConsumerState { dynamic roleId; bool _showFilterRow = false; + Map rowExpanded = {}; Map controllers = {}; List tabHeader = [ 'startDate', @@ -129,7 +136,6 @@ class EnquiryStaffState extends ConsumerState { // "id_proof_file_name": "id_proof.pdf", // "previous_policy_file_name": "previous_policy.pdf", "remarks": controllers["remarks"]?.text, - "assigned_to": selectedStaff, "insurer_id": selectedInsurer, "broker_id": selectedBroker, @@ -168,6 +174,8 @@ class EnquiryStaffState extends ConsumerState { List> filteredBrokerData = []; String? selectedBroker; + final ScrollController _horizontalScrollController = ScrollController(); + //---------------------- Assign Staff Ends -------------------------------- // //---------------------- Enquiry TAB Initializations Ends -------------------------------- // @@ -262,6 +270,13 @@ class EnquiryStaffState extends ConsumerState { ); } + @override + void dispose() { + _horizontalScrollController.dispose(); + // ... dispose other controllers ... + super.dispose(); + } + Future refrshfilterDateRange() async { print('refrshfilterDateRange'); // setState(() async { @@ -577,10 +592,6 @@ class EnquiryStaffState extends ConsumerState { width: 500, height: 300, color: Colors.white, - // width: MediaQuery.of(context).size.width * 0.4, - // height: MediaQuery.of(context).size.height * 0.4, - // width: MediaQuery.of(context).size.width * 0.7, - // height: MediaQuery.of(context).size.height * 0.8, child: QuotationPopUpTab(id: id, onRefresh: refrshfilterDateRange), ), ); @@ -618,92 +629,62 @@ class EnquiryStaffState extends ConsumerState { controllers["regNo"]?.text = item['reg_no']; controllers["remarks"]?.text = item['remarks']; + selectedInsurer = item['insurer_id']; + selectedStaff = item['assigned_to']; setState(() {}); - - // setState(() { - // isEditingRow[id] = true; - // rowControllers[id] = { - // 'agent_name': TextEditingController(text: item['agent_name'] ?? ''), - // 'insurer_name': TextEditingController(text: item['insurer_name'] ?? ''), - // 'reg_no': TextEditingController(text: item['reg_no'] ?? ''), - // 'premium_amount': TextEditingController( - // text: item['premium_amount'] ?? '', - // ), - // 'payment_mode': TextEditingController(text: item['payment_mode'] ?? ''), - // }; - // }); } - void handleSave(int id) { - // final controllers = rowControllers[id]; - // if (controllers == null) return; - if (!_validateRequiredFields()) return; - setState(() { - // final index = getStaffData.indexWhere( - // (e) => e['id'].toString() == id.toString(), - // ); - // if (index != -1) { - // getStaffData[index] = { - // ...getStaffData[index], - // 'agent_name': controllers['agent_name']?.text, - // 'insurer_name': controllers['insurer_name']?.text, - // 'reg_no': controllers['reg_no']?.text, - // 'premium_amount': controllers['premium_amount']?.text, - // 'payment_mode': controllers['payment_mode']?.text, - // }; - // } - // - // if (index != -1) { - // print("EDITField - ${getStaffData[index]}"); - // getStaffData[index] = { - // ...getStaffData[index], - // 'agent_id': selectedAgent, - // "name": controllers["name"]?.text, - // "mobile": controllers["mobile"]?.text, - // "email": controllers["email"]?.text, - // "reg_no": controllers["regNo"]?.text, - // "vehicle_type_id": selectedVehicleType, - // }; - // } + void handleSave(dynamic id) { + print('SAVE -- $id'); + // final ids = int.tryParse(id.toString()) ?? 0; - isEditingRow[id] = false; - rowControllers.remove(id); - }); + idPrimary = id; handleEnqTabSave(); } void handleCancel(int id) { - setState(() { - // Check if this is a new unsaved row - final row = getStaffData.firstWhere( - (item) => item['id'].toString() == id.toString(), - orElse: () => {}, - ); + print('handleCancel id - $id'); - // If policy_number is empty, it's a new row - remove it - if (row.isNotEmpty && - (row['policy_number'] == null || - row['policy_number'] == '' || - row['policy_number'] == '-')) { - getStaffData.removeWhere( + final bool isNewRow = + int.tryParse(id.toString()) != null && + int.parse(id.toString()) > 1000000000000; + + print('isNewRow - $isNewRow'); + + setState(() { + if (isNewRow) { + // Check if this is a new unsaved row + final row = getStaffData.firstWhere( (item) => item['id'].toString() == id.toString(), + orElse: () => {}, ); - originalData.removeWhere( - (item) => item['id'].toString() == id.toString(), - ); - filteredData.removeWhere( - (item) => item['id'].toString() == id.toString(), - ); - rowControllers.remove(id); + + // If policy_number is empty, it's a new row - remove it + if (row.isNotEmpty + // (row['policy_number'] == null || + // row['policy_number'] == '' || + // row['policy_number'] == '-') + ) { + getStaffData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + originalData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + filteredData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + rowControllers.remove(id); + } } // Turn off editing mode isEditingRow.remove(id); - // Clear idPrimary if it matches - if (idPrimary.toString() == id.toString()) { - idPrimary = null; - } + // // Clear idPrimary if it matches + // if (idPrimary.toString() == id.toString()) { + // idPrimary = null; + // } }); } @@ -719,216 +700,295 @@ class EnquiryStaffState extends ConsumerState { selectedVehicle = null; // ✅ fallback } - return SizedBox( - height: 35, - width: MediaQuery.of(context).size.width * 0.08, - child: DropdownSearch>( - key: dropDownSelectStaffKey, - // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, - selectedItem: selectedVehicle, - items: (filter, infiniteScrollProps) { - return filteredStaffDataEnqAsgn; - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Assigned To', style: _subLabelTimeStyle), + SizedBox(height: 5), + SizedBox( + height: 30, + width: MediaQuery.of(context).size.width * 0.08, + child: DropdownSearch>( + key: dropDownSelectStaffKey, + // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, + selectedItem: selectedVehicle, + items: (filter, infiniteScrollProps) { + return filteredStaffDataEnqAsgn; + }, - itemAsString: (val) => val['name'].toString(), - compareFn: (item, selectedItem) => - item['id'] == selectedItem['id'], // ✅ compare by id - validator: (val) { - if (val == null) { - return "Required"; // ✅ error message - } - return null; - }, - decoratorProps: DropDownDecoratorProps( - decoration: - AppInputDecorations.dropdownDecoration( - label: "Select Staff Member", - ).copyWith( - filled: true, - fillColor: Colors.white, // 👈 makes the dropdown input white - isDense: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: const BorderSide(color: Colors.black, width: 0.1), + itemAsString: (val) => val['name'].toString(), + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: const BorderSide(color: Colors.black, width: 0.1), - ), - contentPadding: EdgeInsets.symmetric( - horizontal: 2, - vertical: 6, - ), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem != null ? selectedItem['name'].toString() : "", - style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), - overflow: TextOverflow.ellipsis, - maxLines: 1, - softWrap: false, - ), - ), - popupProps: PopupProps.menu( - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), - menuProps: MenuProps( - backgroundColor: - Colors.white, // 👈 sets dropdown background to white - ), - showSearchBox: true, - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - filled: true, - fillColor: Colors.white, - hintText: "Search Staff ...", - hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.white), // 👈 Normal border - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.white, - width: 1.5, - ), // 👈 Focused border + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), ), ), - ), - // constraints: BoxConstraints(), - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - // color: isSelected ? Colors.blue.withOpacity(0.1) : null, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Staff Member", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0XFF6366F1), + width: 0.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0XFF6366F1), + width: 0.5, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, child: Text( - item['name'].toString(), - style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins( + fontSize: 11, + color: Color(0XFF6366F1), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, ), - ); - }, - ), + ), + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Staff ...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + ), - onChanged: (val) { - if (val != null) { - print("Selected Staff : ${val['name']}"); - print("Id: ${val['id']}"); - selectedStaffName = val['name']; - selectedStaff = val['id']; - // controllers['agentId']?.text = val['agent_code']; - // agentId = agent['id']; - } - }, - ), + onChanged: (val) { + if (val != null) { + print("Selected Staff : ${val['name']}"); + print("Id: ${val['id']}"); + selectedStaffName = val['name']; + selectedStaff = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], ); } - Widget buildInsurer(BuildContext context) { + Widget buildInsurer(BuildContext context, {fromHeader = true}) { Map? selectedInsurerd = filteredInsurersData.firstWhere( (item) => item['id'].toString() == selectedInsurer, orElse: () => {}, ); - return Container( - height: 35, - width: MediaQuery.of(context).size.width * 0.08, - child: DropdownSearch>( - key: dropDownKeyInsurerEnqAsgn, - selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, - items: (filter, infiniteScrollProps) { - return filteredInsurersData; - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Insurer', style: _subLabelTimeStyle), + SizedBox(height: 5), + Container( + height: 30, + width: fromHeader + ? MediaQuery.of(context).size.width * 0.1 + : MediaQuery.of(context).size.width * 0.08, + child: DropdownSearch>( + key: dropDownKeyInsurerEnqAsgn, + selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, + items: (filter, infiniteScrollProps) { + return filteredInsurersData; + }, - itemAsString: (val) => val['name'].toString(), // what to show - compareFn: (item, selectedItem) => - item['id'] == selectedItem['id'], // ✅ compare by id - validator: (val) { - if (val == null) { - return "Required"; // ✅ error message - } - return null; - }, - - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem != null ? selectedItem['name'].toString() : "", - style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), - overflow: TextOverflow.ellipsis, - maxLines: 1, - softWrap: false, - ), - ), - decoratorProps: DropDownDecoratorProps( - decoration: - AppInputDecorations.dropdownDecoration( - label: "Select Insurer", - ).copyWith( - filled: true, - fillColor: Colors.white, // 👈 makes the dropdown input white - isDense: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: const BorderSide(color: Colors.black, width: 0.1), + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: const BorderSide(color: Colors.black, width: 0.1), - ), - contentPadding: EdgeInsets.symmetric( - horizontal: 2, - vertical: 6, - ), - ), - ), - - popupProps: PopupProps.menu( - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), - menuProps: MenuProps( - backgroundColor: - Colors.white, // 👈 sets dropdown background to white - ), - showSearchBox: true, - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - filled: true, - fillColor: Colors.white, - hintText: "Search Insurer...", - hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.white), // 👈 Normal border - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.white, - width: 1.5, - ), // 👈 Focused border + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), ), ), - ), - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - // color: isSelected ? Colors.blue.withOpacity(0.1) : null, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, child: Text( - item['name'].toString(), - style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins( + fontSize: 11, + color: Color(0XFF6366F1), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, ), - ); - }, - // constraints: BoxConstraints(), - ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Insurer", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0XFF6366F1), + width: 0.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0XFF6366F1), + width: 0.5, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), - onChanged: (val) { - if (val != null) { - print("Selected Insurer : ${val['name']}"); - print("Id: ${val['id']}"); - selectedInsurer = val['id']; - // controllers['agentId']?.text = val['agent_code']; - // agentId = agent['id']; - } - }, - ), + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Insurer...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + // constraints: BoxConstraints(), + ), + + onChanged: (val) { + if (val != null) { + print("Selected Insurer : ${val['name']}"); + print("Id: ${val['id']}"); + selectedInsurer = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], ); } @@ -938,7 +998,7 @@ class EnquiryStaffState extends ConsumerState { orElse: () => {}, ); return SizedBox( - height: 35, + height: 30, width: MediaQuery.of(context).size.width * 0.07, child: DropdownSearch>( key: dropDownKeyBroker, @@ -956,6 +1016,20 @@ class EnquiryStaffState extends ConsumerState { } return null; }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( @@ -983,7 +1057,7 @@ class EnquiryStaffState extends ConsumerState { borderSide: const BorderSide(color: Colors.black, width: 0.1), ), contentPadding: EdgeInsets.symmetric( - horizontal: 2, + horizontal: 8, vertical: 6, ), ), @@ -1134,150 +1208,224 @@ class EnquiryStaffState extends ConsumerState { } } - Widget buildAgentName(BuildContext context) { + Widget buildAgentName(BuildContext context, {fromHeader = true}) { Map? selectedAgntName = filteredAgentData.firstWhere( (item) => item['id'].toString() == selectedAgent, orElse: () => {}, ); - return SizedBox( - width: MediaQuery.of(context).size.width * 0.08, - height: 35, - child: DropdownSearch>( - key: dropDownKeyAgent, - selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null, - items: (filter, infiniteScrollProps) { - return filteredAgentData; - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Partner', style: _subLabelTimeStyle), + SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.08, + // width: fromHeader + // ? MediaQuery.of(context).size.width * 0.11 + // : MediaQuery.of(context).size.width * 0.08, + height: 30, + child: DropdownSearch>( + key: dropDownKeyAgent, + selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null, + items: (filter, infiniteScrollProps) { + return filteredAgentData; + }, - itemAsString: (val) => val['name'].toString(), // what to show - compareFn: (item, selectedItem) => - item['id'] == selectedItem['id'], // ✅ compare by id - validator: (val) { - if (val == null) { - return "Required"; // ✅ error message - } - return null; - }, - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem != null - ? selectedItem['name'].toString() - : "", // ✅ FIXED - style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), - overflow: TextOverflow.ellipsis, - maxLines: 1, - softWrap: false, - ), - ), - decoratorProps: DropDownDecoratorProps( - decoration: - AppInputDecorations.dropdownDecoration( - label: "Select Partner", - ).copyWith( - filled: true, - fillColor: Colors.white, // 👈 makes the dropdown input white - isDense: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: const BorderSide(color: Colors.black, width: 0.1), + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: const BorderSide(color: Colors.black, width: 0.1), - ), - contentPadding: EdgeInsets.symmetric( - horizontal: 1, - // vertical: 6, - ), - ), - ), - - popupProps: PopupProps.menu( - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), - menuProps: MenuProps( - backgroundColor: - Colors.white, // 👈 sets dropdown background to white - ), - showSearchBox: true, - - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - filled: true, - fillColor: Colors.white, - hintText: "Search Partner...", - hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.white), // 👈 Normal border - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.white, - width: 1.5, - ), // 👈 Focused border + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), ), ), - ), - - // constraints: BoxConstraints(), - itemBuilder: (context, item, isDisabled, isSelected) { - return Container( - // color: isSelected ? Colors.blue.withOpacity(0.1) : null, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - item['name'].toString(), - style: GoogleFonts.inter(fontSize: 12, color: Colors.black), - ), - Text( - item['agent_code'].toString(), - style: GoogleFonts.inter(fontSize: 11, color: Colors.grey), - ), - ], + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null + ? selectedItem['name'].toString() + : "", // ✅ FIXED + style: GoogleFonts.poppins( + fontSize: 11, + color: Color(0XFF6366F1), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, ), - ); - }, - ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Partner Name", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0XFF6366F1), + width: 0.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0XFF6366F1), + width: 0.5, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), - onChanged: (val) { - if (val != null) { - print("Selected Partner : ${val['name']}"); - print("Id: ${val['id']}"); - selectedAgent = val['id']; - // controllers['agentId']?.text = val['agent_code']; - // agentId = agent['id']; - } - }, - ), + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Partner Name", + hintStyle: GoogleFonts.inter( + fontSize: 10, + color: Colors.grey, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + item['name'].toString(), + style: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + ), + Text( + item['agent_code'].toString(), + style: GoogleFonts.inter( + fontSize: 11, + color: Colors.grey, + ), + ), + ], + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected Partner : ${val['name']}"); + print("Id: ${val['id']}"); + selectedAgent = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], ); } Widget buildVehicleNumber(BuildContext context) { - return ThemedFormInlineField( - controller: controllers['regNo']!, - borderColor: Colors.black, - isdense: true, - inputFormatters: [ - UpperCaseTextFormatter(), // 👈 custom formatter for uppercase - FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Vehicle Number', style: _subLabelTimeStyle), + SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.08, + child: ThemedFormInlineField( + controller: controllers['regNo']!, + hintText: 'Vehicle Number', + borderColor: Color(0XFF6366F1), + textColr: Color(0XFF6366F1), + isdense: true, + inputFormatters: [ + UpperCaseTextFormatter(), // 👈 custom formatter for uppercase + FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')), + ], + validator: (value) => Validators.requiredVechileNum(value, "regNo"), + widthNone: true, + ), + ), ], - validator: (value) => Validators.requiredVechileNum(value, "regNo"), - widthNone: true, ); } - Widget buildInsuredName(BuildContext context) { - return ThemedFormInlineField( - controller: controllers['name']!, - // errorText: fieldErrors['name'], - borderColor: Colors.black, - isdense: true, - validator: (value) => Validators.requiredField(value, "name"), - widthNone: true, + Widget buildInsuredName(BuildContext context, {fromHeader = true}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Insured', style: _subLabelTimeStyle), + SizedBox(height: 5), + SizedBox( + width: fromHeader + ? MediaQuery.of(context).size.width * 0.1 + : MediaQuery.of(context).size.width * 0.08, + child: ThemedFormInlineField( + controller: controllers['name']!, + hintText: 'Insured Name', + textColr: Color(0XFF6366F1), + // errorText: fieldErrors['name'], + borderColor: Color(0XFF6366F1), + isdense: true, + validator: (value) => Validators.requiredField(value, "name"), + widthNone: true, + ), + ), + ], ); } @@ -1291,7 +1439,7 @@ class EnquiryStaffState extends ConsumerState { return SizedBox( width: MediaQuery.of(context).size.width * 0.07, - height: 35, + height: 30, child: DropdownSearch>( key: dropDownKey, selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, @@ -1309,13 +1457,28 @@ class EnquiryStaffState extends ConsumerState { compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem != null ? selectedItem['vehicle_type'].toString() : "", // ✅ FIXED - style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 11, color: Color(0XFF6366F1)), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, @@ -1353,14 +1516,21 @@ class EnquiryStaffState extends ConsumerState { ), showSearchBox: true, searchFieldProps: TextFieldProps( + padding: const EdgeInsets.all(1.0), decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Vehicle Type...", - hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), + hintStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black), + labelStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.white), // 👈 Normal border ), + isDense: true, // reduces height + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 8, // reduce this value for smaller height + ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, @@ -1397,46 +1567,81 @@ class EnquiryStaffState extends ConsumerState { } Widget buildEmail(BuildContext context) { - return ThemedFormInlineField( - controller: controllers['email']!, - borderColor: Colors.black, - isdense: true, - validator: (value) => Validators.nonReqemail(value, "email"), - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Email', style: _subLabelTimeStyle), + SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.1, + child: ThemedFormInlineField( + controller: controllers['email']!, + borderColor: Color(0XFF6366F1), + textColr: Color(0XFF6366F1), + hintText: 'Email', + isdense: true, + validator: (value) => Validators.nonReqemail(value, "email"), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')), + ], + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.16, + ), + ), ], - txtwidth: ResponsiveLayout.isMobile(context) - ? null - : MediaQuery.of(context).size.width * 0.26, ); } Widget buildPhNumber(BuildContext context) { - return ThemedFormInlineField( - controller: controllers['mobile']!, - borderColor: Colors.black, - isdense: true, - validator: (value) => Validators.nonReqphone(value, "phone"), - inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[ 0-9]'))], - txtwidth: ResponsiveLayout.isMobile(context) - ? null - : MediaQuery.of(context).size.width * 0.26, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Mobile', style: _subLabelTimeStyle), + SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.1, + child: ThemedFormInlineField( + controller: controllers['mobile']!, + borderColor: Color(0XFF6366F1), + textColr: Color(0XFF6366F1), + hintText: 'Mobile', + isdense: true, + validator: (value) => Validators.nonReqphone(value, "phone"), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[ 0-9]')), + ], + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.26, + ), + ), + ], ); } Widget buildRemarks(BuildContext context) { - return ThemedFormField( - // maxLength: 500, - enableBorderWidth: 0.1, - controller: controllers['remarks']!, - borderColor: Colors.black, - verticalPad: 8, - horizonalPad: 10, - isdense: true, - // keyboardType: TextInputType.multiline, - txtwidth: ResponsiveLayout.isMobile(context) - ? null - : MediaQuery.of(context).size.width * 0.1, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Remarks', style: _subLabelTimeStyle), + SizedBox(height: 5), + ThemedFormField( + // maxLength: 500, + enableBorderWidth: 0.5, + hintText: 'Remarks', + controller: controllers['remarks']!, + borderColor: Color(0XFF6366F1), + textColr: Color(0XFF6366F1), + verticalPad: 10, + horizonalPad: 10, + isdense: true, + // keyboardType: TextInputType.multiline, + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.1, + ), + ], ); } @@ -1454,10 +1659,12 @@ class EnquiryStaffState extends ConsumerState { final uploadWidget = ThemedUploadField( hintText: hintText ?? "Upload Document", txtwidth: width, + // txtwidth: MediaQuery.of(context).size.width * 0.08, padVertical: 6, padHorizontal: 8, - backgroundColor: Color(0xffD9EBE8), + backgroundColor: Colors.white, + // backgroundColor: Color(0xffD9EBE8), txtName: label, isTxtBtnCase: true, onFileSelected: onFileSelected, @@ -1475,137 +1682,150 @@ class EnquiryStaffState extends ConsumerState { } Widget buildUploadRCDocument(BuildContext context) { - return buildResponsiveUploadField( - label: "RC", - width: 60, - hintText: selectedRCFile, - onFileSelected: (fileName, file) { - setState(() { - docUploadedRCFile = file; - selectedRCFileName = fileName; - }); - ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); - }, - showDownload: docUploadedRCFile != null || rcFileUrlFromApi != null, - onRemove: () { - setState(() { - docUploadedRCFile = null; - rcFileUrlFromApi = null; - selectedRCFileName = null; - }); - }, - onDownload: () => apiService.downloadFile( - // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', - apiUrl: - 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc', + return SizedBox( + // width: MediaQuery.of(context).size.width * 0.08, + child: buildResponsiveUploadField( + label: "RC", + width: 60, + hintText: selectedRCFile, + onFileSelected: (fileName, file) { + setState(() { + docUploadedRCFile = file; + selectedRCFileName = fileName; + }); + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: docUploadedRCFile != null || rcFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedRCFile = null; + rcFileUrlFromApi = null; + selectedRCFileName = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc', - apiId: selectedId.toString(), + apiId: selectedId.toString(), - localFile: docUploadedRCFile, - fileName: 'RC', + localFile: docUploadedRCFile, + fileName: 'RC', + ), ), ); } Widget buildUploadIDDocument(BuildContext context) { - return buildResponsiveUploadField( - label: "ID Proof", - width: 80, - hintText: selectedIdProof, - onFileSelected: (fileName, file) { - setState(() { - docUploadedIDProof = file; - // selectedFileNames = fileName; - }); + return SizedBox( + // width: MediaQuery.of(context).size.width * 0.08, + child: buildResponsiveUploadField( + label: "ID Proof", + width: 80, + hintText: selectedIdProof, + onFileSelected: (fileName, file) { + setState(() { + docUploadedIDProof = file; + // selectedFileNames = fileName; + }); - ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); - }, - showDownload: docUploadedIDProof != null || idProofFileUrlFromApi != null, - onRemove: () { - setState(() { - docUploadedIDProof = null; - idProofFileUrlFromApi = null; - // selectedFileNames = null; - }); - }, - onDownload: () => apiService.downloadFile( - // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', - apiUrl: - 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof', + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: + docUploadedIDProof != null || idProofFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedIDProof = null; + idProofFileUrlFromApi = null; + // selectedFileNames = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof', - apiId: selectedId.toString(), - localFile: docUploadedIDProof, - fileName: 'Id_Proof', + apiId: selectedId.toString(), + localFile: docUploadedIDProof, + fileName: 'Id_Proof', + ), ), ); } Widget buildUploadPolicyDocument(BuildContext context) { - return buildResponsiveUploadField( - width: 100, - label: "PrevPolicy", - hintText: selectedPrevPolicy, - onFileSelected: (fileName, file) { - setState(() { - docUploadedPrevPolicy = file; - // selectedFileNames = fileName; - }); - ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); - }, - showDownload: - docUploadedPrevPolicy != null || prevPolicyFileUrlFromApi != null, - onRemove: () { - setState(() { - docUploadedPrevPolicy = null; - prevPolicyFileUrlFromApi = null; - // selectedFileNames = null; - }); - }, - onDownload: () => apiService.downloadFile( - // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', - apiUrl: - 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy', + return SizedBox( + // width: MediaQuery.of(context).size.width * 0.08, + child: buildResponsiveUploadField( + width: 100, + label: "PrevPolicy", + hintText: selectedPrevPolicy, + onFileSelected: (fileName, file) { + setState(() { + docUploadedPrevPolicy = file; + // selectedFileNames = fileName; + }); + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: + docUploadedPrevPolicy != null || prevPolicyFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedPrevPolicy = null; + prevPolicyFileUrlFromApi = null; + // selectedFileNames = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy', - apiId: selectedId.toString(), - localFile: docUploadedPrevPolicy, - fileName: 'Previous_Policy', + apiId: selectedId.toString(), + localFile: docUploadedPrevPolicy, + fileName: 'Previous_Policy', + ), ), ); } Widget buildDocRC(BuildContext context, selectedEnquiryId) { - return InkWell( - onTap: () { - print("Upload $selectedEnquiryId"); - final selectedId = selectedEnquiryId; - // final path = - // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; - final path = - 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc'; - print("Uploadpath $path"); - apiService.getPdfDownload(path, selectedId); - }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: const Color(0xFF425B5B), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Tooltip( - message: 'Download RC', - child: Text( - "RC", - style: GoogleFonts.inter( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Colors.white, + return SizedBox( + width: MediaQuery.of(context).size.width * 0.16, + child: InkWell( + onTap: () { + print("Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc'; + print("Uploadpath $path"); + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Tooltip( + message: 'Download RC', + child: Text( + "RC", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), ), ), - ), - ], + ], + ), ), ), ); @@ -1748,6 +1968,7 @@ class EnquiryStaffState extends ConsumerState { try { await createUserData(dataSet); // API call + // ToastHelper.showInfoToast(context, "Enquiry saved successfully"); } catch (e) { showDialog( @@ -1821,6 +2042,288 @@ class EnquiryStaffState extends ConsumerState { ); } + List _buildPopupDownload(BuildContext context, selectId) { + bool isMobile = ResponsiveLayout.isMobile(context); + return [ + // editing + // ? + Container( + // color: Colors.red.shade50, + padding: EdgeInsets.only(top: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + + children: [ + buildUploadRCDocument(context), + SizedBox(width: 3), + buildUploadIDDocument(context), + SizedBox(width: 3), + buildUploadPolicyDocument(context), + ], + ), + ), + ]; + } + + Widget _buildSubRow(context, selectId, id) { + return Wrap( + spacing: 12, + runSpacing: 12, + children: [ + buildAgentName(context, fromHeader: false), + buildSelectStaffMem(context), + + buildInsurer(context), + buildInsuredName(context), + buildVehicleNumber(context), + + buildEmail(context), + buildPhNumber(context), + buildRemarks(context), + + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Builder( + builder: (buttonContext) => Material( + // color: Color(0xFFFFFCF2), + color: Color(0xFFF8F9FF), + child: InkWell( + hoverColor: Color(0xFFF8F9FF), + // hoverColor: const Color(0xFFFFFCF2), + // hoverColor: const Color(0xFFEAF6F4), + onTap: () async { + // ✅ Get overlay and button position RELATIVE to InkWell + final RenderBox button = + buttonContext.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(buttonContext).context.findRenderObject() + as RenderBox; + + final Offset position = button.localToGlobal( + Offset.zero, + ancestor: overlay, + ); + + await showMenu( + context: buttonContext, + position: RelativeRect.fromLTRB( + position.dx, + position.dy + button.size.height, + overlay.size.width, + 0, + ), + items: [ + PopupMenuItem( + child: Column( + mainAxisSize: MainAxisSize.min, + children: _buildPopupDownload( + buttonContext, + selectId, + ), + ), + ), + ], + // color: Colors.white, + // color: Color(0xFFFFFCF2), + color: Color(0xFFF8F9FF), + ); + }, + child: Tooltip( + message: 'Click To Upload Documents', + + waitDuration: const Duration(milliseconds: 500), + showDuration: const Duration(seconds: 2), + child: Container( + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(50), + ), + child: Icon( + Icons.upload_file, + color: Color(0XFF6366F1), + // editing ? Icons.upload_file : Icons.download_for_offline, + // color: editing ? Colors.b lue : Colors.green, + size: 20, + ), + ), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: IconButton( + tooltip: 'Save', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 24, minHeight: 24), + icon: const Icon( + Icons.check, + color: Colors.green, + size: 28, // smaller icon + ), + splashRadius: 14, // ripple radius + onPressed: () => { + setState(() { + rowExpanded[id] = !(rowExpanded[id] ?? false); + }), + handleSave(selectId), + }, + ), + ), + ], + ); + } + + List _buildPopupDownload_BTN( + BuildContext context, + dynamic data, + id, + regNum, + ) { + bool isMobile = ResponsiveLayout.isMobile(context); + return [ + Row( + children: [ + buildDocRC_BTN(context, isMobile, id), + Spacer(), + // SizedBox(width: 3), + buildDocIdProof_BTN(context, isMobile, id), + Spacer(), + buildDocPrevPolicy_BTN(context, isMobile, id), + ], + ), + ]; + } + + Widget buildDocRC_BTN( + BuildContext context, + bool isMobile, + selectedEnquiryId, + ) { + return GestureDetector( + onTap: () { + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc'; + + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "RC", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ?isMobile ? null : SizedBox(width: 2), + // isMobile ? const Spacer() : const SizedBox(width: 15), + const Icon( + Icons.file_download_outlined, + size: 16, + color: Colors.white, + ), + ], + ), + ), + ); + } + + Widget buildDocIdProof_BTN( + BuildContext context, + bool isMobile, + selectedEnquiryId, + ) { + return GestureDetector( + onTap: () { + print("Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof'; + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "ID Proof", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ?isMobile ? null : SizedBox(width: 2), + // const SizedBox(width: 15), + Icon(Icons.file_download_outlined, size: 16, color: Colors.white), + ], + ), + ), + ); + } + + Widget buildDocPrevPolicy_BTN( + BuildContext context, + bool isMobile, + selectedEnquiryId, + ) { + return GestureDetector( + onTap: () { + print("QD597 Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy'; + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Previous Policy", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ?isMobile ? null : SizedBox(width: 2), + // isMobile ? const Spacer() : const SizedBox(width: 15), + Icon(Icons.file_download_outlined, size: 16, color: Colors.white), + ], + ), + ), + ); + } + Future createUserData(Map userData) async { final bool isNewRow = idPrimary != null && @@ -1936,52 +2439,52 @@ class EnquiryStaffState extends ConsumerState { title: "Enquiries", body: Container( // color: Colors.yellow.shade50, + // color: Color(0xFFFCFCFC), + // color: Colors.white, + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(12), + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.08), + // blurRadius: 8, + // spreadRadius: 1, + // offset: const Offset(0, 2), // soft drop shadow + // ), + // ], + // ), width: MediaQuery.of(context).size.width, - padding: EdgeInsets.all(3.0), + // padding: EdgeInsets.all(12.0), 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), - // onPressed: () async { - // context.go(AppRoutes.dashboard); - // final prefs = await SharedPreferences.getInstance(); - // await prefs.remove('dashboardKeyProvider'); - // await prefs.remove('dashboardStatusProvider'); - // await prefs.remove('dashboardStaffIdProvider'); - // }, - // splashRadius: 18, - // padding: const EdgeInsets.all(4), - // hoverColor: Colors.black12, - // constraints: const BoxConstraints(), - // ), - // ), - // const SizedBox(width: 5), // spacing between icon and text - // Text( - // "Enquiries", - // style: TextStyle( - // fontSize: 18, - // fontWeight: FontWeight.w600, - // ), - // ), - // ], - // ), - // ), - // ), + RaiseEnqForm( + key: raiseEnqKey, + onSubmit: (dataSet) async { + try { + await createUserData(dataSet); // <-- API call here + print("Enquiry saved successfully"); + // RESET FORM + raiseEnqKey.currentState?.resetFormFields(); + } catch (e) { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('Error'), + content: Text(e.toString()), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + } + }, + ), + // SizedBox(height: 5), ResponsiveLayout.isMobile(context) ? Container( @@ -1993,17 +2496,20 @@ class EnquiryStaffState extends ConsumerState { ), ), ) - : Expanded( - child: Container( - width: MediaQuery.of(context).size.width, - // padding: EdgeInsets.symmetric( - // horizontal: 8.0, - // vertical: 2.0, - // ), - child: _buildContent(context), - ), + : + // Expanded( + // child: + Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.69, + // padding: EdgeInsets.symmetric( + // horizontal: 8.0, + // vertical: 2.0, + // ), + child: _buildContent(context), ), + // ), Container( // height: 20, width: MediaQuery.of(context).size.width, @@ -2056,89 +2562,59 @@ class EnquiryStaffState extends ConsumerState { } Widget _buildContent(BuildContext context) { + final isDesktop = !ResponsiveLayout.isMobile(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // if (ResponsiveLayout.isMobile(context)) ...[ - // Container( - // padding: EdgeInsets.all(8.0), - // color: Color(0xffD9EBE8), - // child: DateFilterRow( - // key: ValueKey(SelectedStatus ?? ''), - // onFilterStaff: (val) { - // print('Selected Filterd STAFF Id - $val'); - // SelectedStaffId = val; - // }, - // selectedStaffId: SelectedStaffId, - // role: roleId, - // id: userId, - // selectedStatusVal: SelectedStatus, - // startController: controllers['startDate']!, - // endController: controllers['endDate']!, - // formKey: _formKey, - // isMobile: ResponsiveLayout.isMobile(context), - // onStatusChanged: (val) { - // SelectedStatus = val; // update parent - // }, - // - // onFilter: () { - // // call your filter logic - // filterDateRange(); - // }, - // onRefresh: () { - // // call your refresh logic - // refrshfilterDateRange(); - // }, - // ), - // ), - // ], + SizedBox(height: 10), Container( // height: 40, - // color: Colors.pink, + // color: Color(0xffF1F5F9), child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, 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), - onPressed: () async { - context.go(AppRoutes.dashboard); - final prefs = await SharedPreferences.getInstance(); - await prefs.remove('dashboardKeyProvider'); - await prefs.remove('dashboardStatusProvider'); - await prefs.remove('dashboardStaffIdProvider'); - }, - splashRadius: 18, - padding: const EdgeInsets.all(4), - hoverColor: Colors.black12, - constraints: const BoxConstraints(), - ), - ), - const SizedBox(width: 5), // spacing between icon and text - Text( - "Enquiries", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ), + // 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), + // onPressed: () async { + // context.go(AppRoutes.dashboard); + // final prefs = await SharedPreferences.getInstance(); + // await prefs.remove('dashboardKeyProvider'); + // await prefs.remove('dashboardStatusProvider'); + // await prefs.remove('dashboardStaffIdProvider'); + // }, + // splashRadius: 18, + // padding: const EdgeInsets.all(4), + // hoverColor: Colors.black12, + // constraints: const BoxConstraints(), + // ), + // ), + // const SizedBox(width: 5), // spacing between icon and text + // Text( + // "Enquiries", + // style: TextStyle( + // fontSize: 18, + // fontWeight: FontWeight.w600, + // ), + // ), + // ], + // ), + // ), + // ), + // Spacer(), if (_showFilterRow) ...[ DateFilterRow( key: ValueKey(SelectedStatus ?? ''), @@ -2173,6 +2649,8 @@ class EnquiryStaffState extends ConsumerState { ? Icons.close_fullscreen : Icons.filter_alt_outlined, ), + iconSize: 18, + color: const Color(0xFF94A3B8), tooltip: _showFilterRow ? 'Hide Filters' : 'Show Filters', onPressed: () { setState(() { @@ -2180,22 +2658,18 @@ class EnquiryStaffState extends ConsumerState { }); }, ), - Spacer(), ThemedSearchField( hintText: 'Search', - backgroundColor: Color(0xFFF6F8F8), + backgroundColor: Color(0xFFFFFFFF), + // backgroundColor: Colors.white, onChanged: filterData, - txtHeight: 35, + txtHeight: 30, controller: _searchStaffController, txtwidth: ResponsiveLayout.isMobile(context) ? MediaQuery.of(context).size.width * 0.7 : MediaQuery.of(context).size.width * 0.13, ), - - ResponsiveLayout.isMobile(context) - ? Spacer() - : SizedBox(width: 10), - + SizedBox(width: 10), ExportBtn( sheetName: "Enquiry", fileName: "Enquiry_list", @@ -2228,203 +2702,284 @@ class EnquiryStaffState extends ConsumerState { "status", ], ), - SizedBox(width: 10), - GestureDetector( - onTap: () async { - // final prefs = await SharedPreferences.getInstance(); - // - // await prefs.remove('enqAgentDataId'); - // ref.read(enquiryIdProvider.notifier).state = null; - // context.go(AppRoutes.tabEnquiry); - // print('Export'); - - // Generate a unique ID for the new row - final newId = DateTime.now().millisecondsSinceEpoch; - - // Clear all existing form controllers - for (String field in tabHeader) { - controllers[field]?.clear(); - } - - // Reset dropdown selections - selectedVehicleType = null; - selectedAgent = null; - selectedInsurer = null; - - // Reset file uploads - docUploadedRCFile = null; - docUploadedIDProof = null; - docUploadedPrevPolicy = null; - selectedRCFile = null; - selectedIdProof = null; - selectedPrevPolicy = null; - - setState(() { - // Create a new empty row data - final newRow = { - 'id': newId.toString(), // Keep as String for consistency - 'created_on': '', - 'agent_name': '', - 'agent_id': '', - 'assigned_to_name': '', - 'insurer_name': '', - 'insured_name': '', - 'reg_no': '', - 'vehicle_type': '', - 'vehicle_type_id': '', - 'email': '', - 'mobile': '', - 'remarks': '', - 'updated_on': '', - 'premium_amount': '', - 'payment_mode': '', - 'policy_number': '', - 'status': '', - }; - - // Insert at the beginning of ALL lists - getStaffData.insert(0, newRow); - originalData.insert(0, newRow); - filteredData.insert(0, newRow); - - // Set to editing mode immediately - isEditingRow[newId] = true; - - // Store the ID as string - idPrimary = newId.toString(); - selectedId = newId.toString(); - - // Reset to first page to see the new row - currentPage = 1; - }); - }, - child: Container( - padding: EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: Color(0xFF425B5B), - borderRadius: BorderRadius.circular(8.0), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Tooltip( - message: 'Raise Enquiry', - child: Icon(Icons.add, color: Colors.white, size: 15), - ), - - // if (!ResponsiveLayout.isMobile(context)) ...[ - // SizedBox(width: 10), - // Text( - // 'Raise Enquiry', - // style: GoogleFonts.inter( - // color: Colors.white, - // fontWeight: FontWeight.w600, - // fontSize: 14, - // ), - // ), - // ], - ], - ), - ), - ), ], ), ), - // SizedBox(height: 10), - // Container( - // decoration: BoxDecoration( - // color: const Color(0xFFEDF6F5), - // borderRadius: BorderRadius.circular(6), - // ), - // padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - // child: Row( - // children: const [ - // Expanded(flex: 1, child: Text('S.No', style: _headerStyle)), - // Expanded( - // flex: 2, - // child: Text('Received Date', style: _headerStyle), - // ), - // Expanded(flex: 2, child: Text('Partner *', style: _headerStyle)), - // Expanded( - // flex: 2, - // child: Text('Assigned To *', style: _headerStyle), - // ), - // Expanded(flex: 2, child: Text('Broker *', style: _headerStyle)), - // Expanded(flex: 3, child: Text('Insurer *', style: _headerStyle)), - // Expanded( - // flex: 3, - // child: Text('Insured Name *', style: _headerStyle), - // ), - // Expanded( - // flex: 2, - // child: Text('Vehicle No *', style: _headerStyle), - // ), - // Expanded( - // flex: 2, - // child: Text('Vehicle Type *', style: _headerStyle), - // ), - // Expanded(flex: 2, child: Text('Email', style: _headerStyle)), - // Expanded(flex: 2, child: Text('Mobile', style: _headerStyle)), - // Expanded(flex: 2, child: Text('Documents', style: _headerStyle)), - // Expanded(flex: 2, child: Text('Remarks', style: _headerStyle)), - // Expanded( - // flex: 2, - // child: Text('Assigned Date', style: _headerStyle), - // ), - // Expanded(flex: 2, child: Text('Premium', style: _headerStyle)), - // Expanded( - // flex: 2, - // child: Text('Payment Mode', style: _headerStyle), - // ), - // Expanded( - // flex: 2, - // child: Text('Policy Number', style: _headerStyle), - // ), - // Expanded(flex: 2, child: Text('Status', style: _headerStyle)), - // Expanded(flex: 1, child: Text('Action', style: _headerStyle)), - // ], + SizedBox(height: 5), + // SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: Container( + // width: MediaQuery.of(context).size.width, + // // width: double.infinity, + // // height: 40, + // decoration: BoxDecoration( + // color: Colors.white, + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.08), + // blurRadius: 8, + // spreadRadius: 1, + // offset: const Offset(0, 2), // soft drop shadow + // ), + // ], + // ), + // // color: Colors.white, + // + // // color: Color(0xFFE8F5F3), + // // color: Color(0xFFE8F5F3), + // child: _buildTableHeader(isDesktop), // ), // ), ResponsiveLayout.isMobile(context) ? _buildDataTable(context) - : Expanded(child: _buildDataTable(context)), + : + // Expanded( + // child: + Container( + height: MediaQuery.of(context).size.height * 0.55, + decoration: BoxDecoration( + color: Colors.white, + // border: Border.all(color: Colors.grey, width: 0.1), + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.08), + // blurRadius: 8, + // spreadRadius: 1, + // offset: const Offset(0, 2), // soft drop shadow + // ), + // ], + ), + child: _buildDataTable(context), + ), + // ), ], ); } - List _buildPopupDownload(BuildContext context, selectId, editing) { - bool isMobile = ResponsiveLayout.isMobile(context); - return [ - editing - ? Container( - // color: Colors.red.shade50, - padding: EdgeInsets.only(top: 10), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, + // Add this to your EnquiryStaffState class - children: [ - buildUploadRCDocument(context), - SizedBox(width: 3), - buildUploadIDDocument(context), - SizedBox(width: 3), - buildUploadPolicyDocument(context), - ], - ), - ) - : Row( - children: [ - buildDocRC(context, selectId), - SizedBox(width: 3), - buildDocIdProof(context, selectId), - SizedBox(width: 3), - buildDocPrevPolicy(context, selectId), - ], - ), - ]; + // 1. Add group expansion state tracking + Map groupExpanded = { + 'Awaiting Proposal': true, + 'Proposal Created': true, + // 'Proposal Accepted': true, + // 'Proposal Rejected': true, + 'Policy Created': true, + }; + + Map statusGroupMap = { + 'Awaiting Proposal': 'Awaiting Proposal', + 'Proposal Created': 'Proposal Created', + 'Proposal Accepted': 'Proposal Created', + 'Proposal Rejected': 'Proposal Created', + 'Policy Created': 'Policy Created', + }; + + // 2. Add method to group enquiries by status + Map>> get _groupedEnquiries { + final grouped = >>{}; + + // Initialize all status groups + grouped['Awaiting Proposal'] = []; + grouped['Proposal Created'] = []; + grouped['Proposal Accepted'] = []; + grouped['Proposal Rejected'] = []; + grouped['Policy Created'] = []; + + // Initialize only the groups you need + grouped['Awaiting Proposal'] = []; + grouped['Proposal Created'] = []; + grouped['Policy Created'] = []; + + // Group the filtered/paginated data by status + // for (var item in _paginatedData) { + // final status = item['status']?.toString() ?? 'Unknown'; + // if (grouped.containsKey(status)) { + // grouped[status]!.add(item); + // } + // } + + for (var item in _paginatedData) { + final status = item['status']?.toString() ?? 'Unknown'; + + // Map actual status to grouped status + final groupKey = statusGroupMap[status]; + + if (groupKey != null) { + grouped[groupKey]!.add(item); + } + } + + return grouped; } - Widget _buildDataTable(BuildContext context) { + // 3. Add method to get status icon and color + Map _getStatusConfig(String status) { + switch (status) { + case 'Awaiting Proposal': + return {'icon': '🕐', 'color': Color(0xFFfff3cd)}; + case 'Proposal Created': + return {'icon': '📝', 'color': Color(0xFFd1ecf1)}; + // case 'Proposal Accepted': + // return {'icon': '✅', 'color': Color(0xFFd4edda)}; + // case 'Proposal Rejected': + // return {'icon': '❌', 'color': Color(0xFFf8d7da)}; + case 'Policy Created': + return {'icon': '📋', 'color': Color(0xFFcfe2ff)}; + default: + return {'icon': '📄', 'color': Color(0xFFe9ecef)}; + } + } + + // 4. Build group header widget + Widget _buildGroupHeader(String status, int count) { + final config = _getStatusConfig(status); + final isExpanded = groupExpanded[status] ?? true; + + return InkWell( + onTap: () { + setState(() { + groupExpanded[status] = !isExpanded; + }); + }, + child: Container( + decoration: BoxDecoration( + // color: Colors.grey.shade50, + // color: Color(0xFFF0F7F6), + // color: Colors.grey.shade100, + // border: Border.all(color: Colors.grey.shade200, width: 1.6), + // border: Border( + // bottom: BorderSide( + // width: 0.1, + // color: Colors.grey.shade50, + // // color: Color(0xffE0E7E6), + // ), + // ), + ), + + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Row( + children: [ + Icon( + isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, + size: 22, + color: Color(0xFF5A6C7D), + ), + SizedBox(width: 8), + Text( + // '${config['icon']} $status', + '$status', + style: GoogleFonts.inter( + fontSize: 13, + color: Colors.black, + // color: Colors.orange, + fontWeight: FontWeight.w600, + ), + ), + // Spacer(), + SizedBox(width: 8), + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration( + color: Color(0xFFEFF6FF), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '$count', + style: GoogleFonts.inter( + fontSize: 12, + color: Color(0xFF2563EB), + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildDataTable(context) { + final screenWidth = MediaQuery.of(context).size.width; + final isMobile = ResponsiveLayout.isMobile(context); + + // Calculate minimum width based on screen size + double minTableWidth = isMobile ? screenWidth * 1.5 : 1300; + return + // Expanded( + // child: + LayoutBuilder( + builder: (context, constraints) { + // double minWidth = constraints.maxWidth < 1300 + // ? 1300 + // : constraints.maxWidth; + double tableWidth = constraints.maxWidth > minTableWidth + ? constraints.maxWidth + : minTableWidth; + + final isDesktop = !ResponsiveLayout.isMobile(context); + + return ScrollConfiguration( + behavior: const MaterialScrollBehavior().copyWith( + dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch}, + ), + child: SingleChildScrollView( + controller: _horizontalScrollController, // 👈 Shared controller + scrollDirection: Axis.horizontal, + child: SizedBox( + // width: minWidth, + width: tableWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Fixed Header + Container( + decoration: BoxDecoration( + // color: Colors.white, + color: Color(0xFFF1F5F9), + borderRadius: BorderRadius.circular(5), + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.08), + // blurRadius: 8, + // spreadRadius: 1, + // offset: const Offset(0, 2), + // ), + // ], + ), + child: _buildTableHeader(isDesktop), + ), + + // Scrollable Data Content + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + // border: Border.all(color: Colors.grey, width: 0.1), + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.08), + // blurRadius: 8, + // spreadRadius: 1, + // offset: const Offset(0, 2), + // ), + // ], + ), + child: _buildDataTableContent(), // 👈 New method + ), + ), + ], + ), + ), + ), + ); + }, + ); + // ); + } + + Widget _buildDataTableContent() { if (filteredData.isEmpty) { return const SizedBox( height: 50, @@ -2432,90 +2987,146 @@ class EnquiryStaffState extends ConsumerState { ); } - final sortedData = [..._paginatedData]; - final startIndex = (currentPage - 1) * itemsPerPage; - final isDesktop = !ResponsiveLayout.isMobile(context); + final groupedData = _groupedEnquiries; - // Build both-direction scrollable DataTable - return LayoutBuilder( - builder: (context, constraints) { - double minWidth = isDesktop ? constraints.maxWidth : 1300; + return SingleChildScrollView( + scrollDirection: Axis.vertical, // 👈 Only vertical scroll here + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...groupedData.entries.map((entry) { + final status = entry.key; + final items = entry.value; + final isExpanded = groupExpanded[status] ?? true; - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: minWidth), - child: DataTable( - headingRowColor: WidgetStatePropertyAll(Color(0xFFEDF6F5)), - dividerThickness: 0.5, - headingRowHeight: 30, - columnSpacing: isDesktop ? 20.0 : 16.0, - border: TableBorder( - horizontalInside: BorderSide( - width: 0.5, - color: Colors.grey.shade200, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Group Header + if (status != 'Proposal Accepted' && + status != 'Proposal Rejected') ...[ + Container( + // width: double.infinity, + width: MediaQuery.of(context).size.width, + margin: EdgeInsets.fromLTRB(12, 8, 12, 8), + child: _buildGroupHeader(status, items.length), ), - ), - columns: [ - // DataColumn(label: Text('S.No', style: _headerStyle)), - const DataColumn( - label: Text('Received Date', style: _headerStyle), - ), - const DataColumn( - label: Text('Partner *', style: _headerStyle), - ), - const DataColumn( - label: Text('Assigned To *', style: _headerStyle), - ), - const DataColumn( - label: Text('Broker *', style: _headerStyle), - ), - const DataColumn( - label: Text('Insurer *', style: _headerStyle), - ), - const DataColumn( - label: Text('Insured Name *', style: _headerStyle), - ), - const DataColumn( - label: Text('Vehicle No *', style: _headerStyle), - ), - const DataColumn( - label: Text('Vehicle Type *', style: _headerStyle), - ), - const DataColumn(label: Text('Email', style: _headerStyle)), - const DataColumn(label: Text('Mobile', style: _headerStyle)), - const DataColumn( - label: Text('Documents', style: _headerStyle), - ), - const DataColumn(label: Text('Remarks', style: _headerStyle)), - if (roleId == 'manager') ...const [ - DataColumn(label: Text('Action', style: _headerStyle)), - ], - const DataColumn( - label: Text('Assigned Date', style: _headerStyle), - ), - const DataColumn(label: Text('Premium', style: _headerStyle)), - const DataColumn( - label: Text('Payment Mode', style: _headerStyle), - ), - const DataColumn( - label: Text('Policy Number', style: _headerStyle), - ), - const DataColumn(label: Text('Status', style: _headerStyle)), ], - rows: sortedData.asMap().entries.map((entry) { - final index = entry.key; - final item = entry.value; - final sno = startIndex + index + 1; - return _buildDataRow(item, sno); - }).toList(), - ), - ), - ), - ); - }, + // Group rows + if (isExpanded && items.isNotEmpty) + ...items.asMap().entries.map((itemEntry) { + final index = itemEntry.key; + final item = itemEntry.value; + final startIndex = (currentPage - 1) * itemsPerPage; + final sno = startIndex + index + 1; + final id = int.tryParse(item['id'].toString()) ?? 0; + final selectId = item['id'].toString() ?? '0'; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Data row + Container( + width: MediaQuery.of( + context, + ).size.width, // margin: EdgeInsets.all(10.0), + margin: EdgeInsets.fromLTRB(12, 8, 12, 8), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Color(0xFFFFECF1F7)), + // border: Border( + // left: BorderSide(color: Color(0XFFE2E8F0)), + // right: BorderSide(color: Color(0XFFE2E8F0)), + // bottom: BorderSide(color: Color(0XFFE2E8F0)), + // top: BorderSide(color: Color(0XFFE2E8F0)), + // ), + borderRadius: BorderRadius.circular(8), + // boxShadow: [ + // BoxShadow( + // color: Color(0XFFE2E8F0), + // blurRadius: 3, + // spreadRadius: 0.2, + // offset: const Offset(0, 1), // soft drop shadow + // ), + // ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + DataTable( + border: TableBorder.all( + color: Colors.transparent, + width: 0, + ), + // border: TableBorder( + // top: BorderSide.none, + // left: BorderSide.none, + // right: BorderSide.none, + // bottom: BorderSide.none, + // horizontalInside: BorderSide.none, + // verticalInside: BorderSide.none, + // ), + headingRowHeight: 0, + dividerThickness: 0.1, + columnSpacing: 20.0, + dataRowMinHeight: 40, + columns: _buildDataColumns(), + rows: [_buildDataRow(item, sno)], + ), + + // Expanded content + if (rowExpanded[id] ?? false) + Container( + // width: double.infinity, + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + // borderRadius: const BorderRadius.only( + // topLeft: Radius.circular(25), + // topRight: Radius.circular(25), + // ), + border: Border.all( + color: Color(0xFFC7D2FE), + ), + + color: Color(0xFFF8F9FF), + ), + padding: const EdgeInsets.symmetric( + vertical: 14.0, + horizontal: 8.0, + ), + child: _buildSubRow(context, selectId, id), + ), + ], + ), + ), + ], + ); + }).toList(), + + if (isExpanded && items.isEmpty) + if (status != 'Proposal Accepted' && + status != 'Proposal Rejected') + Padding( + padding: EdgeInsets.all(20), + child: Center( + child: Text( + 'No records found', + style: GoogleFonts.inter( + fontSize: 12, + color: Colors.grey, + fontStyle: FontStyle.italic, + ), + ), + ), + ), + ], + ); + }).toList(), + ], + ), ); } @@ -2523,140 +3134,161 @@ class EnquiryStaffState extends ConsumerState { final id = int.tryParse(item['id'].toString()) ?? 0; final selectId = item['id'].toString() ?? '0'; + final bool isNewRow = + int.tryParse(id.toString()) != null && + int.parse(id.toString()) > 1000000000000; + print('idPrimary - $idPrimary'); + print('isNewRow - $isNewRow'); final editing = isEditingRow[id] ?? false; final controllers = rowControllers[id]; return DataRow( cells: [ - // S.No. - // DataCell(Text('$sno', style: _dataBold)), - - // Created On DataCell( - Row( - children: [ - // Text(item['created_on'], style: _dataBold), - Text( - formatDateTimeForTable(item['created_on'], newLine: true), - style: _dataBold, - ), - ], + SizedBox( + width: 80, + child: Row( + children: [ + // Text(item['created_on'], style: _dataBold), + Text( + formatDateTimeForTable(item['created_on'], newLine: true), + style: _dataBoldthm1, + ), + ], + ), ), ), // Agent Name DataCell( - editing - ? buildAgentName(context) - : Text(item['agent_name'] ?? '-', style: _dataBold), + SizedBox( + width: 70, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(item['agent_code'] ?? '-', style: _dataBold), + Text( + item['agent_name'] != null + ? _truncateText(item['agent_name'], 10) + : '-', + style: _tableDataTimeStyle, + ), + ], + ), + ), ), // Assigned To DataCell( - editing - ? buildSelectStaffMem(context) - : Text(item['assigned_to_name'] ?? '-', style: _dataBold), - ), - - DataCell( - editing - ? buildBroker(context) - : Text(item['broker_name'] ?? '-', style: _dataBold), + SizedBox( + width: 100, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + item['assigned_to_name'] != null + ? _truncateText(item['assigned_to_name'], 15) + : '-', + style: _dataBold, + ), + Text(_formatDate(item['updated_on']), style: _dataBoldthm4), + ], + ), + ), ), // Insurer Name DataCell( - editing - ? buildInsurer(context) - : Text(item['insurer_short_name'] ?? '-', style: _dataBold), + SizedBox( + width: 80, + child: Text( + item['insurer_short_name'] ?? '-', + style: _dataBoldthm2, + ), + ), ), DataCell( - editing - ? buildInsuredName(context) - : Text(item['insured_name'] ?? '-', style: _dataBold), + SizedBox( + width: 90, + child: Tooltip( + message: item['insured_name'], + child: Text( + item['insured_name'] != null + ? _truncateText(item['insured_name'], 15) + : '-', + style: _dataBoldthm2, + ), + ), + ), ), // Vehicle No DataCell( - editing - ? buildVehicleNumber(context) - : Text(item['reg_no'] ?? '-', style: _dataBold), - ), - DataCell( - editing - ? buildVehicleType(context) - : Text( - _wrapText(item['vehicle_type'] ?? '-', 13), - style: _dataBold, - softWrap: true, - ), - ), + SizedBox( + width: 85, + child: Builder( + builder: (buttonContext) => Material( + color: Colors.white, + child: InkWell( + // hoverColor: Color(0xFFEAF6F4), + onTap: () async { + // ✅ Get overlay and button position RELATIVE to InkWell + final RenderBox button = + buttonContext.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(buttonContext).context.findRenderObject() + as RenderBox; - DataCell( - editing - ? buildEmail(context) - : Text(item['email'] ?? '-', style: _dataBold), - ), - DataCell( - editing - ? buildPhNumber(context) - : Text(item['mobile'] ?? '-', style: _dataBold), - ), + final Offset position = button.localToGlobal( + Offset.zero, + ancestor: overlay, + ); - DataCell( - Builder( - builder: (buttonContext) => Material( - color: Colors.white, - child: InkWell( - hoverColor: const Color(0xFFEAF6F4), - onTap: () async { - // ✅ Get overlay and button position RELATIVE to InkWell - final RenderBox button = - buttonContext.findRenderObject() as RenderBox; - final RenderBox overlay = - Overlay.of(buttonContext).context.findRenderObject() - as RenderBox; - - final Offset position = button.localToGlobal( - Offset.zero, - ancestor: overlay, - ); - - await showMenu( - context: buttonContext, - position: RelativeRect.fromLTRB( - position.dx, - position.dy + button.size.height, - overlay.size.width, - 0, - ), - items: [ - PopupMenuItem( - child: Column( - mainAxisSize: MainAxisSize.min, - children: _buildPopupDownload( - buttonContext, - selectId, - editing, + await showMenu( + context: buttonContext, + position: RelativeRect.fromLTRB( + position.dx, + position.dy + button.size.height, + overlay.size.width, + // overlay.size.width - position.dx, + 0, + ), + items: [ + PopupMenuItem( + child: Column( + mainAxisSize: MainAxisSize.min, + children: _buildPopupDownload_BTN( + buttonContext, + item, + item['id'], + item['reg_no'], + ), ), ), + ], + color: Colors.white, + ); + }, + child: Tooltip( + message: + 'Click and Download Enquiry Files', // what appears on hover/long press + waitDuration: const Duration(milliseconds: 500), // optional + showDuration: const Duration(seconds: 2), // optional + child: Container( + padding: EdgeInsets.all(4.0), + margin: EdgeInsets.symmetric(vertical: 3), + decoration: BoxDecoration( + color: Color(0xFFF9FAFB), + border: Border.all(color: Color(0xFFE2E8F0)), + borderRadius: BorderRadius.circular(5), ), - ], - color: Colors.white, - ); - }, - child: Tooltip( - message: editing - ? 'Click To Upload Documents' - : 'Click To View Documents', - waitDuration: const Duration(milliseconds: 500), - showDuration: const Duration(seconds: 2), - child: Icon( - editing ? Icons.upload_file : Icons.download_for_offline, - color: editing ? Colors.blue : Colors.green, - size: 22, + child: Text(item['reg_no'] ?? '-', style: _dataBoldthm3), + ), ), + // child: Text(item['reg_no'] ?? '-', style: _dataBold), ), ), ), @@ -2664,138 +3296,174 @@ class EnquiryStaffState extends ConsumerState { ), DataCell( - editing - ? buildRemarks(context) - : Tooltip( - message: item['remarks'] ?? '-', // full content on hover - waitDuration: const Duration(milliseconds: 400), - showDuration: const Duration(seconds: 2), - child: Text( - _truncateText(item['remarks'] ?? '-', 10), - style: _dataBold, - overflow: TextOverflow.ellipsis, + SizedBox( + width: 40.0, + child: Row( + children: [ + Tooltip( + message: + 'Email : ${item["email"]}\n' + 'Mobile : ${item["mobile"]}\n' + 'Remarks : ${item["remarks"]}', + decoration: BoxDecoration( + color: Colors.white, // background color + borderRadius: BorderRadius.circular(6), + border: Border.all(color: Colors.grey, width: 1), + ), + textStyle: GoogleFonts.inter( + color: Colors.black, + fontSize: 12, + ), + + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, // reduce tooltip height + ), + margin: const EdgeInsets.only( + left: 10, + ), // tooltip distance from widget + waitDuration: Duration(milliseconds: 500), + showDuration: Duration(seconds: 2), + child: Icon( + Icons.info_outline_rounded, + size: 15, + color: Colors.blue, ), ), + + IconButton( + padding: EdgeInsets.zero, // remove outer padding + constraints: BoxConstraints( + // reduce clickable area + minWidth: 24, + minHeight: 24, + ), + icon: Icon( + rowExpanded[id] == true ? Icons.edit_off_sharp : Icons.edit, + color: Colors.blue, + size: 15, + ), + + tooltip: "Edit", + onPressed: () { + setState(() { + bool isOpen = rowExpanded[id] ?? false; + + rowExpanded.clear(); // close all + + if (!isOpen) { + rowExpanded[id] = + true; // open only if previously closed + } + // rowExpanded[id] = !(rowExpanded[id] ?? false); + }); + handleEdit(item); + }, + ), + ], + ), + ), ), - // Actions - if (roleId == 'manager') ...[ - DataCell( - // (editing && (item['status'] == 'Awaiting Proposal')) - editing - ? Row( - children: [ - IconButton( - icon: const Icon(Icons.check, color: Colors.green), - tooltip: "Save", - onPressed: () => handleSave(id), - ), - IconButton( - icon: const Icon(Icons.close, color: Colors.red), - tooltip: "Cancel", - onPressed: () => handleCancel(id), - ), - ], - ) - : Row( - children: [ - IconButton( - icon: const Icon(Icons.edit, color: Colors.blue), - tooltip: "Edit", - onPressed: () { - handleEdit(item); - }, - ), - ], - ), - ), - ], // Assigned Date (formatted) - DataCell( - Text( - // item['updated_on'] ?? '-', - formatDateTimeForTable(item['updated_on'], newLine: true), - style: _dataBold, - softWrap: true, - maxLines: 3, - ), - ), + // DataCell( + // SizedBox( + // width: 80, + // child: Text( + // // item['updated_on'] ?? '-', + // formatDateTimeForTable(item['updated_on'], newLine: true), + // style: _dataBold, + // softWrap: true, + // maxLines: 3, + // ), + // ), + // ), // Premium DataCell( - // ((item['premium_amount'] != null) && - // (item['premium_amount'] != '') && - // ((item['status'] != 'Awaiting Proposal') || - // (item['status'] == 'Proposal Created') || - // (item['status'] == 'Proposal Rejected'))) (item['status'] == 'Proposal Created' || item['status'] == 'Proposal Rejected') - ? InkWell( - onTap: () { - // handle approval logic + ? SizedBox( + width: 80, + child: InkWell( + onTap: () { + // handle approval logic - handleProposalAccept(context, item['id']); - }, - child: Text( - 'Click To\nApprove', - style: GoogleFonts.inter( - color: Colors.green, - fontSize: 11, - fontWeight: FontWeight.w700, + handleProposalAccept(context, item['id']); + }, + child: Text( + 'Click To\nApprove', + style: GoogleFonts.inter( + color: Colors.green, + fontSize: 11, + fontWeight: FontWeight.w700, + ), ), ), ) - : Text( - item['premium_amount']?.toString() ?? '-', - style: _dataBold, + : SizedBox( + width: 80, + child: Text( + item['premium_amount']?.toString() ?? '-', + style: _dataBold, + ), ), ), // Payment Mode - DataCell(Text(item['payment_mode'] ?? '-', style: _dataBold)), - - // Policy Number DataCell( - Text( - item['policy_number'] ?? '-', - style: _dataBold, - softWrap: true, - maxLines: 3, + SizedBox( + width: 80, + child: Text(item['payment_mode'] ?? '-', style: _dataBold), ), ), - // Status - // DataCell(Text(item['status'] ?? '-', style: _dataBold)), + // Policy Number DataCell( - Material( - color: Colors.white, - child: InkWell( - hoverColor: Color(0xFFEAF6F4), - onTap: () async { - var status = item['status']; - var enqId = item['id']; + SizedBox( + width: 90, + child: Text( + item['policy_number'] ?? '-', + style: _dataBold, + softWrap: true, + maxLines: 3, + ), + ), + ), - final prefs = await SharedPreferences.getInstance(); + DataCell( + SizedBox( + width: 80, + child: Material( + color: Colors.white, + child: InkWell( + hoverColor: Color(0xFFEAF6F4), + onTap: () async { + var status = item['status']; + var enqId = item['id']; - // ✅ Remove old value (if any) - await prefs.remove('enqStaffDataId'); + final prefs = await SharedPreferences.getInstance(); - // ✅ Save the new id - await prefs.setString('enqStaffDataId', enqId.toString()); - ref.read(quotationStaffIdProvider.notifier).state = enqId; - buildStatusActions(context, status, enqId); - }, - child: Tooltip( - message: 'Click to View or Process Enquiry', // - waitDuration: const Duration(milliseconds: 500), // optional - showDuration: const Duration(seconds: 2), // optional - child: Text( - item['status'] ?? '-', - style: _dataBold, - overflow: TextOverflow.ellipsis, // + // ✅ Remove old value (if any) + await prefs.remove('enqStaffDataId'); + + // ✅ Save the new id + await prefs.setString('enqStaffDataId', enqId.toString()); + ref.read(quotationStaffIdProvider.notifier).state = enqId; + buildStatusActions(context, status, enqId); + }, + child: Tooltip( + message: 'Click to View or Process Enquiry', // + waitDuration: const Duration(milliseconds: 500), // optional + showDuration: const Duration(seconds: 2), // optional + child: Text( + item['status'] ?? '-', + style: _dataBold, + // overflow: TextOverflow.clip, // + ), ), + // child: Text(item['status'] ?? '-', style: _dataBold), ), - // child: Text(item['status'] ?? '-', style: _dataBold), ), ), ), @@ -2803,41 +3471,137 @@ class EnquiryStaffState extends ConsumerState { ); } - static final _dataBold = TextStyle( - fontSize: 12, + // Add this helper method to generate DataColumns + List _buildDataColumns() { + return [ + DataColumn(label: Text('Received Date', style: _headerStyle)), + DataColumn(label: Text('Partner *', style: _headerStyle)), + DataColumn(label: Text('Assigned To *', style: _headerStyle)), + // const DataColumn(label: Text('Broker *', style: _headerStyle)), + DataColumn(label: Text('Insurer *', style: _headerStyle)), + DataColumn(label: Text('Insured Name *', style: _headerStyle)), + DataColumn(label: Text('Vehicle No *', style: _headerStyle)), + DataColumn(label: Text('Action', style: _headerStyle)), + // const DataColumn(label: Text('Assigned Date', style: _headerStyle)), + DataColumn(label: Text('Premium', style: _headerStyle)), + DataColumn(label: Text('Payment Mode', style: _headerStyle)), + DataColumn(label: Text('Policy Number', style: _headerStyle)), + DataColumn(label: Text('Status', style: _headerStyle)), + ]; + } + + // 6. Build table header + Widget _buildTableHeader(bool isDesktop) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + _buildHeaderCell('Received Date', 120), + _buildHeaderCell('Partner *', 80), + _buildHeaderCell('Assigned To *', 110), + // _buildHeaderCell('Broker *', 100), + _buildHeaderCell('Insurer *', 90), + _buildHeaderCell('Insured Name *', 120), + _buildHeaderCell('Vehicle No *', 100), + _buildHeaderCell('Action', 60), + // _buildHeaderCell('Assigned Date', 100), + _buildHeaderCell('Premium', 100), + _buildHeaderCell('Payment Mode', 100), + _buildHeaderCell('Policy Number', 120), + _buildHeaderCell('Status', 100), + ], + ), + ); + } + + Widget _buildHeaderCell(String label, double width) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 4), + child: SizedBox( + width: width, + child: Text( + label, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF1E293B), + // color: Colors.black87, + ), + ), + ), + ); + } + + static final _dataBold = GoogleFonts.inter( + fontSize: 11, fontWeight: FontWeight.w500, color: Colors.black, - // color: Color(0xFF000000), + // color: Color(0xFF000000), + ); + static final _dataBoldthm1 = GoogleFonts.inter( + fontSize: 11, + + fontWeight: FontWeight.w500, + color: Colors.black, + // color: Color(0xFF000000), + ); + + static final _dataBoldthm2 = GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w500, + // color: Colors.black, + color: Color(0XFF334155), + ); + static final _dataBoldthm3 = GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w500, + // color: Colors.black, + color: Color(0XFF334155), + ); + static final _dataBoldthm4 = GoogleFonts.inter( + fontSize: 9, + fontWeight: FontWeight.w500, + // color: Colors.black, + color: Color(0XFF334155), ); final _tableDataTimeStyle = GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w400, + color: Colors.black, + // color: Color(0XFF94A3B8), ); - static final _dataBoldsmall = TextStyle( + + final _subLabelTimeStyle = GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w400, + color: Color(0XFF6366F1), + ); + static final _dataBoldsmall = GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w400, color: Color(0xFF000000), ); - static final _dataSub = TextStyle( + static final _dataSub = GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w300, color: Color(0xFF585757), ); - static const _headerStyle = TextStyle( - color: Colors.black, + static final _headerStyle = GoogleFonts.inter( + // color: Colors.black, + color: Color(0xFF1E293B), fontWeight: FontWeight.w500, ); - static const _cardheaderStyle = TextStyle( + static final _cardheaderStyle = GoogleFonts.inter( color: Colors.black, fontWeight: FontWeight.w600, fontSize: 12, ); - static final TextStyle _textStyle = TextStyle( + static final TextStyle _textStyle = GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w600, ); diff --git a/lib/presentation/screens/staff/Enquiry/enquiry_inline_old.dart b/lib/presentation/screens/staff/Enquiry/enquiry_inline_old.dart new file mode 100644 index 0000000..ffebb48 --- /dev/null +++ b/lib/presentation/screens/staff/Enquiry/enquiry_inline_old.dart @@ -0,0 +1,3108 @@ +import 'dart:ui'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/cupertino.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/staff/Enquiry/quotationPopUpAccptReject.dart'; +import 'package:nhance_partner/presentation/screens/staff/Enquiry/tabs/tab.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../../core/config/env.dart'; +import '../../../../core/routing/routes.dart'; + +import '../../../../core/services/api_service.dart'; +import 'package:universal_html/html.dart' as html; + +import '../../../../data/services/auth_service.dart'; +import '../../../../data/utils/Pagination.dart'; +import '../../../../data/utils/toastNotification.dart'; +import '../../../../data/utils/validators.dart'; +import '../../../layouts/main_layout.dart'; +import '../../../layouts/responsive_layout.dart'; +import '../../../providers/manager_provider.dart'; +import '../../../providers/quotation_staff_proivder.dart'; +import '../../../providers/userRoleProvider.dart'; +import '../../../themes/indicators/customizd_file_upload.dart'; +import '../../../themes/indicators/export_btn.dart'; +import '../../../themes/indicators/input_field_decoration.dart'; +import '../../../themes/indicators/search_field_theme.dart'; +import '../../../themes/indicators/text_field_theme.dart'; +import '../../../themes/indicators/text_field_theme_inline_editor.dart' + hide UpperCaseTextFormatter; +import '../../../widgets/custom_Stdate_EnDate_Filter.dart'; +import '../assignStaff.dart'; + +class EnquiryListStaffInline extends ConsumerStatefulWidget { + const EnquiryListStaffInline({super.key}); + @override + ConsumerState createState() => EnquiryStaffState(); +} + +class EnquiryStaffState extends ConsumerState { + int currentPage = 1; + int itemsPerPage = 10; + late ApiService apiService; + dynamic userId; + dynamic idPrimary; + dynamic managerId; + dynamic dashboardKey; + dynamic SelectedStatus; + dynamic SelectedStaffId; + dynamic handlerId; + String? _token; + final _formKey = GlobalKey(); + Map fieldErrors = {}; + + // List> dataVal = []; + List> getStaffData = []; + List> originalData = []; + List> filteredData = []; + bool isLoading = false; + dynamic roleId; + bool _showFilterRow = false; + + Map controllers = {}; + List tabHeader = [ + 'startDate', + 'endDate', + 'name', + 'email', + 'mobile', + 'code', + 'address', + 'regNo', + 'remarks', + ]; + + Map isEditingRow = {}; + Map> rowControllers = {}; + + //---------------------- Enquiry TAB Initializations Starts -------------------------------- // + + List> getVehicleTypeData = []; + List> filteredVechicleData = []; + + List> getAgentListData = []; + List> filteredAgentData = []; + + List> getInsurersData = []; + List> filteredInsurersData = []; + + String? selectedVehicleType; + String? selectedInsurer; + String? selectedAgent; + PlatformFile? docUploadedRCFile; + PlatformFile? docUploadedIDProof; + PlatformFile? docUploadedPrevPolicy; + + String? selectedRCFile; + String? selectedIdProof; + String? selectedPrevPolicy; + + String? selectedRCFileName; + + String? rcFileUrlFromApi; + String? idProofFileUrlFromApi; + String? prevPolicyFileUrlFromApi; + + String? selectedId; + + Map dataDetails() { + final data = { + "agent_id": ((roleId == 'handler') || (roleId == 'manager')) + ? selectedAgent + : userId, + "name": controllers["name"]?.text, + "mobile": controllers["mobile"]?.text, + "email": controllers["email"]?.text, + "reg_no": controllers["regNo"]?.text, + "vehicle_type_id": selectedVehicleType, + "is_data_created_by_handler": roleId == 'handler' ? '1' : '0', + "is_data_created_by_manager": roleId == 'manager' ? '1' : '0', + // "insurer_id": selectedInsurer, + // "rc_file_name": "rc_doc.pdf", + // "id_proof_file_name": "id_proof.pdf", + // "previous_policy_file_name": "previous_policy.pdf", + "remarks": controllers["remarks"]?.text, + + "assigned_to": selectedStaff, + "insurer_id": selectedInsurer, + "broker_id": selectedBroker, + "manager_id": managerId, + "created_by": userId, + }; + + return data; + } + + final GlobalKey>> dropDownKey = + GlobalKey>>(); + final GlobalKey>> + dropDownKeyInsurer = GlobalKey>>(); + final GlobalKey>> dropDownKeyAgent = + GlobalKey>>(); + //---------------------- Assign Staff starts -------------------------------- // + + final GlobalKey>> + dropDownKeyInsurerEnqAsgn = + GlobalKey>>(); + + final GlobalKey>> dropDownKeyBroker = + GlobalKey>>(); + final GlobalKey>> + dropDownSelectStaffKey = + GlobalKey>>(); + + List> getStaffDetailsDataEnqAsgn = []; + List> filteredStaffDataEnqAsgn = []; + + String? selectedStaff; + String? selectedRegNum; + String? selectedStaffName; + List> getBrokerData = []; + List> filteredBrokerData = []; + String? selectedBroker; + + //---------------------- Assign Staff Ends -------------------------------- // + //---------------------- Enquiry TAB Initializations Ends -------------------------------- // + + @override + void initState() { + super.initState(); + + apiService = ApiService(); + + for (String field in tabHeader) { + controllers[field] = TextEditingController(); + } + _initializeToken(); + + Future.microtask(() async { + // final id = ref.read(managerIdProvider); + roleId = ref.read(userRoleProvider); + userId = ref.read(userIdProvider); + managerId = ref.read(managerIdProvider); + + final prefs = await SharedPreferences.getInstance(); + dashboardKey = prefs.getString('dashboardKeyProvider'); + final dashboardStatus = prefs.getString('dashboardStatusProvider'); + final dashboardStaffId = prefs.getString('dashboardStaffIdProvider'); + + print('handlerIdENQ - $handlerId'); + print("C72 => r : $roleId | uId: $userId !mID : $managerId "); + + print('dashboardKey - $dashboardKey'); + print('dashboardStatus - $dashboardStatus'); + print('dashboardStaffId - $dashboardStaffId'); + + if (managerId != null && + dashboardKey != null && + dashboardStatus != null && + dashboardStaffId != null) { + print("DASHBOARD STATus-$dashboardStatus -- $dashboardStaffId -"); + SelectedStatus = dashboardStatus; + SelectedStaffId = dashboardStaffId; + getStaffList( + managerId, + roleId, + SelectedStatus: dashboardStatus, + SelectedStaffId: dashboardStaffId, + ); + } else { + print('ELSE'); + getStaffList(managerId, roleId); + } + if (managerId != null) { + print('managerId - $managerId'); + getAgentList(managerId); + } + + final userID = ref.watch(userIdProvider); + + print("managerId - $managerId"); + if (userID != null) { + print('hansles'); + getStaffDetailsForEnquiryAssignment(userID); + } + }); + + getVehicleType(); + getInsurers(); + getBroker(); + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + print("APISERTOKEN - $_token"); + } + + void refresh() { + final id = ref.read(managerIdProvider); + final roleId = ref.read(userRoleProvider); + final userId = ref.read(userIdProvider); + + print("C82 => r : $roleId | mId: $id | uId: $userId "); + if (managerId != null) { + getStaffList(managerId, roleId); + } + } + + void filterDateRange() { + // ✅ Call your API + getStaffList( + managerId, + roleId, + SelectedStatus: SelectedStatus ?? '', + SelectedStaffId: SelectedStaffId ?? '', + ); + } + + Future refrshfilterDateRange() async { + print('refrshfilterDateRange'); + // setState(() async { + final prefs = await SharedPreferences.getInstance(); + + // ✅ Clear any old data + await prefs.remove('dashboardKeyProvider'); + await prefs.remove('dashboardStatusProvider'); + await prefs.remove('dashboardStaffIdProvider'); + SelectedStatus = ''; + SelectedStaffId = null; + controllers['startDate']!.clear(); + controllers['endDate']!.clear(); + + controllers['startDate']?.text = ''; + controllers['endDate']?.text = ''; + // Reset the FormField validation + _formKey.currentState?.reset(); + // }); + + final int parsedManagerId = int.tryParse(managerId.toString()) ?? 0; + print('refrshfilterDateRange 1'); + getStaffList( + // managerId, + parsedManagerId, + roleId, + SelectedStatus: SelectedStatus ?? '', + SelectedStaffId: SelectedStaffId ?? '', + ); + print('refrshfilterDateRange 2'); + } + + Future getInsurers() async { + print('Insurers called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('Insurers'); + + if (response['status'] == 200) { + print('getInsurers - ${response['data']}'); + setState(() { + getInsurersData = List>.from(response['data']); + print('API Data - $getInsurersData'); + + filteredInsurersData = List.from(getInsurersData); + print('originalData - $filteredInsurersData'); + }); + } else { + getInsurersData = []; + filteredInsurersData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getBroker() async { + print('getBroker called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('Broker'); + + if (response['status'] == 200) { + print('getBroker - ${response['data']}'); + setState(() { + getBrokerData = List>.from(response['data']); + print('API Data - $getBrokerData'); + + filteredBrokerData = List.from(getBrokerData); + print('originalData - $filteredBrokerData'); + }); + } else { + getBrokerData = []; + filteredBrokerData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getStaffDetailsForEnquiryAssignment(int id) async { + print('getStaffDetailsForEnquiryAssignment called By handler'); + setState(() { + isLoading = true; + }); + + try { + // final response = await apiService.fetchStaffUserList(id, role); + final response = await apiService.fetchStaffListForEnquiryAssignDropDown( + id, + roleId, + ); + + if (response['status'] == 'success') { + print('getStaffDetailsForEnquiryAssignment - ${response['data']}'); + setState(() { + getStaffDetailsDataEnqAsgn = List>.from( + response['data'], + ); + print('API Data - $getStaffDetailsDataEnqAsgn'); + + filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn); + print('originalData - $filteredStaffDataEnqAsgn'); + }); + } else { + getStaffDetailsDataEnqAsgn = []; + filteredStaffDataEnqAsgn = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getStaffList( + int managerId, + role, { + String fromDate = '', + String toDate = '', + String SelectedStatus = '', + String SelectedStaffId = '', + }) async { + print('A72 => Fns called => $managerId | $role'); + setState(() { + isLoading = true; + }); + + dynamic fromDt; + dynamic toDt; + + final fromDateVal = controllers['startDate']?.text ?? ''; + final toDateVal = controllers['endDate']?.text ?? ''; + final dateFormat = DateFormat('dd-MM-yyyy'); + + if (dashboardKey != 'fromDashboard' && + fromDateVal == '' && + toDateVal == '') { + print("ENQ LIST PAGE"); + + final today = DateTime.now(); + fromDt = dateFormat.format(today.subtract(Duration(days: 15))); + toDt = dateFormat.format(today); + + print('Enq FROM TO - $fromDt - $toDt'); + } else { + fromDt = fromDateVal; + toDt = toDateVal; + } + + try { + final response = await apiService.fetchEnquiryList( + managerId, + role, + fromDate: fromDt, + toDate: toDt, + selectedStatus: SelectedStatus != null ? SelectedStatus : '', + selectedStaffId: SelectedStaffId ?? '', + ); + + if (response['status'] == 'success') { + final data = response['data']; + final fromDate = response['from_date'] ?? ''; + final toDate = response['to_date'] ?? ''; + + print('FromDate : $fromDate'); + print('ToDate : $toDate'); + + setState(() { + controllers['startDate']?.text = fromDate; + controllers['endDate']?.text = toDate; + if (data is List) { + getStaffData = List>.from(data); + } else if (data is Map) { + getStaffData = [Map.from(data)]; + } else { + getStaffData = []; + } + originalData = getStaffData; + filteredData = List.from(originalData); + }); + } else { + getStaffData = []; + originalData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + 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); + } + + void filterData(String query) { + print("FilterDAta - $query"); + setState(() { + filteredData = getStaffData.where((item) { + // final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive"; + + return (item['agent_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['reg_no'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insurer_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['assigned_to_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insured_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['premium_amount'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['payment_mode'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (_formatDate(item['created_on']) ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (_formatDate(item['updated_on']) ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['policy_number'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['status'] ?? '-').toLowerCase().contains(query.toLowerCase()); + }).toList(); + }); + } + + final TextEditingController _searchStaffController = TextEditingController(); + + String _formatDate(String rawDate) { + try { + final dateTime = DateTime.parse(rawDate); + return DateFormat( + 'dd-MM-yyyy HH:mm', + ).format(dateTime); // 24-hour format + } catch (e) { + return rawDate; // fallback if parsing fails + } + } + + Future handleStaff( + BuildContext context, + dynamic data, + id, + regNum, + ) async { + showDialog( + context: context, + builder: (ctx) => AssignStaffDialog( + enquiryPrimaryId: id, + regNum: regNum, + userId: userId, + onSubmit: (value) { + debugPrint("New assignY: $value"); + refresh(); + }, + ), + ); + } + + Future handleProposalAccept(BuildContext context, String id) async { + final result = showDialog( + context: context, + barrierDismissible: true, // allows tapping outside to close + builder: (ctx) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + insetPadding: const EdgeInsets.all(24), + child: Container( + width: 500, + height: 300, + color: Colors.white, + // width: MediaQuery.of(context).size.width * 0.4, + // height: MediaQuery.of(context).size.height * 0.4, + // width: MediaQuery.of(context).size.width * 0.7, + // height: MediaQuery.of(context).size.height * 0.8, + child: QuotationPopUpTab(id: id, onRefresh: refrshfilterDateRange), + ), + ); + }, + ); + + print("Dialog result: $result"); + (); + } + + void handleEdit(Map item) { + print("handleEdit"); + + final id = int.tryParse(item['id'].toString()) ?? 0; + idPrimary = item['id']; + + print("handleEdit - $idPrimary"); + setState(() { + selectedId = idPrimary; + isEditingRow[id] = true; + }); + for (final field in tabHeader) { + // Only set if controller exists + if (controllers.containsKey(field)) { + controllers[field]!.text = item[field] != null + ? item[field].toString() + : ''; // fallback to empty string + } + } + + //dropdowns or IDs + selectedVehicleType = item['vehicle_type_id']; + selectedAgent = item['agent_id']; + managerId = item['manager_id']; + controllers["regNo"]?.text = item['reg_no']; + controllers["remarks"]?.text = item['remarks']; + + setState(() {}); + + // setState(() { + // isEditingRow[id] = true; + // rowControllers[id] = { + // 'agent_name': TextEditingController(text: item['agent_name'] ?? ''), + // 'insurer_name': TextEditingController(text: item['insurer_name'] ?? ''), + // 'reg_no': TextEditingController(text: item['reg_no'] ?? ''), + // 'premium_amount': TextEditingController( + // text: item['premium_amount'] ?? '', + // ), + // 'payment_mode': TextEditingController(text: item['payment_mode'] ?? ''), + // }; + // }); + } + + void handleSave(int id) { + // final controllers = rowControllers[id]; + // if (controllers == null) return; + if (!_validateRequiredFields()) return; + setState(() { + // final index = getStaffData.indexWhere( + // (e) => e['id'].toString() == id.toString(), + // ); + // if (index != -1) { + // getStaffData[index] = { + // ...getStaffData[index], + // 'agent_name': controllers['agent_name']?.text, + // 'insurer_name': controllers['insurer_name']?.text, + // 'reg_no': controllers['reg_no']?.text, + // 'premium_amount': controllers['premium_amount']?.text, + // 'payment_mode': controllers['payment_mode']?.text, + // }; + // } + // + // if (index != -1) { + // print("EDITField - ${getStaffData[index]}"); + // getStaffData[index] = { + // ...getStaffData[index], + // 'agent_id': selectedAgent, + // "name": controllers["name"]?.text, + // "mobile": controllers["mobile"]?.text, + // "email": controllers["email"]?.text, + // "reg_no": controllers["regNo"]?.text, + // "vehicle_type_id": selectedVehicleType, + // }; + // } + + isEditingRow[id] = false; + rowControllers.remove(id); + }); + handleEnqTabSave(); + } + + void handleCancel(int id) { + print('handleCancel id - $id'); + + final bool isNewRow = + int.tryParse(id.toString()) != null && + int.parse(id.toString()) > 1000000000000; + + print('isNewRow - $isNewRow'); + + setState(() { + if (isNewRow) { + // Check if this is a new unsaved row + final row = getStaffData.firstWhere( + (item) => item['id'].toString() == id.toString(), + orElse: () => {}, + ); + + // If policy_number is empty, it's a new row - remove it + if (row.isNotEmpty + // (row['policy_number'] == null || + // row['policy_number'] == '' || + // row['policy_number'] == '-') + ) { + getStaffData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + originalData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + filteredData.removeWhere( + (item) => item['id'].toString() == id.toString(), + ); + rowControllers.remove(id); + } + } + + // Turn off editing mode + isEditingRow.remove(id); + + // // Clear idPrimary if it matches + // if (idPrimary.toString() == id.toString()) { + // idPrimary = null; + // } + }); + } + + //-------------------------------Assign Logics staff start ---------------------------// + + Widget buildSelectStaffMem(ctx) { + Map? selectedVehicle; + try { + selectedVehicle = filteredStaffDataEnqAsgn.firstWhere( + (item) => item['id'].toString() == selectedStaff, + ); + } catch (e) { + selectedVehicle = null; // ✅ fallback + } + + return SizedBox( + height: 30, + width: MediaQuery.of(context).size.width * 0.08, + child: DropdownSearch>( + key: dropDownSelectStaffKey, + // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, + selectedItem: selectedVehicle, + items: (filter, infiniteScrollProps) { + return filteredStaffDataEnqAsgn; + }, + + itemAsString: (val) => val['name'].toString(), + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Staff Member", + ).copyWith( + filled: true, + fillColor: Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Staff ...", + hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected Staff : ${val['name']}"); + print("Id: ${val['id']}"); + selectedStaffName = val['name']; + selectedStaff = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ); + } + + Widget buildInsurer(BuildContext context, {fromHeader = true}) { + Map? selectedInsurerd = filteredInsurersData.firstWhere( + (item) => item['id'].toString() == selectedInsurer, + orElse: () => {}, + ); + return Container( + height: 30, + width: fromHeader + ? MediaQuery.of(context).size.width * 0.1 + : MediaQuery.of(context).size.width * 0.08, + child: DropdownSearch>( + key: dropDownKeyInsurerEnqAsgn, + selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, + items: (filter, infiniteScrollProps) { + return filteredInsurersData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: AppInputDecorations.dropdownDecoration(label: "Insurer") + .copyWith( + filled: true, + fillColor: Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Insurer...", + hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + // constraints: BoxConstraints(), + ), + + onChanged: (val) { + if (val != null) { + print("Selected Insurer : ${val['name']}"); + print("Id: ${val['id']}"); + selectedInsurer = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ); + } + + Widget buildBroker(BuildContext context) { + Map? selectedBrokers = filteredBrokerData.firstWhere( + (item) => item['id'].toString() == selectedBroker, + orElse: () => {}, + ); + return SizedBox( + height: 30, + width: MediaQuery.of(context).size.width * 0.07, + child: DropdownSearch>( + key: dropDownKeyBroker, + selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null, + items: (filter, infiniteScrollProps) { + return filteredBrokerData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Broker", + ).copyWith( + filled: true, + fillColor: Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Broker...", + hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + // constraints: BoxConstraints(), + ), + + onChanged: (val) { + if (val != null) { + print("Selected Broker : ${val['name']}"); + print("Id: ${val['id']}"); + selectedBroker = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ); + } + + // -------------------------------Assign Logics staff end---------------------------// + + //---------------------- Enquiry TAB Logins Starts -------------------------------- // + + Map dataEnquiryTabDetails() { + final data = { + "agent_id": ((roleId == 'handler') || (roleId == 'manager')) + ? selectedAgent + : userId, + "name": controllers["name"]?.text, + "mobile": controllers["mobile"]?.text, + "email": controllers["email"]?.text, + "reg_no": controllers["regNo"]?.text, + "vehicle_type_id": selectedVehicleType, + "is_data_created_by_handler": roleId == 'handler' ? '1' : '0', + "is_data_created_by_manager": roleId == 'manager' ? '1' : '0', + // "insurer_id": selectedInsurer, + // "rc_file_name": "rc_doc.pdf", + // "id_proof_file_name": "id_proof.pdf", + // "previous_policy_file_name": "previous_policy.pdf", + "remarks": controllers["remarks"]?.text, + "manager_id": managerId, + "created_by": userId, + }; + + return data; + } + + Future getVehicleType() async { + print('getClaimList called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('vehicleType'); + + if (response['status'] == 200) { + print('getVehicleTypeData - ${response['data']}'); + setState(() { + getVehicleTypeData = List>.from( + response['data'], + ); + print('API Data - $getVehicleTypeData'); + + filteredVechicleData = List.from(getVehicleTypeData); + print('originalData - $filteredVechicleData'); + }); + } else { + getVehicleTypeData = []; + filteredVechicleData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getAgentList(id) async { + print('getAgentListData called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchAgentNameDropDown(id); + print('getAgentListData called response'); + print('get Agent- ${response['data']}'); + if (response['status'] == 'success') { + print('get Agent- ${response['data']}'); + setState(() { + getAgentListData = List>.from(response['data']); + print('API Data - $getAgentListData'); + + filteredAgentData = List.from(getAgentListData); + print('originalAgentData - $filteredAgentData'); + }); + } else { + getAgentListData = []; + filteredAgentData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Widget buildAgentName(BuildContext context, {fromHeader = true}) { + Map? selectedAgntName = filteredAgentData.firstWhere( + (item) => item['id'].toString() == selectedAgent, + orElse: () => {}, + ); + return SizedBox( + width: fromHeader + ? MediaQuery.of(context).size.width * 0.11 + : MediaQuery.of(context).size.width * 0.08, + height: 30, + child: DropdownSearch>( + key: dropDownKeyAgent, + selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null, + items: (filter, infiniteScrollProps) { + return filteredAgentData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null + ? selectedItem['name'].toString() + : "", // ✅ FIXED + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Partner Name", + ).copyWith( + filled: true, + fillColor: Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Partner Name", + hintStyle: GoogleFonts.inter(fontSize: 10, color: Colors.grey), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + Text( + item['agent_code'].toString(), + style: GoogleFonts.inter(fontSize: 11, color: Colors.grey), + ), + ], + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected Partner : ${val['name']}"); + print("Id: ${val['id']}"); + selectedAgent = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ); + } + + Widget buildVehicleNumber(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.08, + child: ThemedFormInlineField( + controller: controllers['regNo']!, + hintText: 'Vehicle Number', + borderColor: Colors.black, + isdense: true, + inputFormatters: [ + UpperCaseTextFormatter(), // 👈 custom formatter for uppercase + FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')), + ], + validator: (value) => Validators.requiredVechileNum(value, "regNo"), + widthNone: true, + ), + ); + } + + Widget buildInsuredName(BuildContext context, {fromHeader = true}) { + return SizedBox( + width: fromHeader + ? MediaQuery.of(context).size.width * 0.1 + : MediaQuery.of(context).size.width * 0.08, + child: ThemedFormInlineField( + controller: controllers['name']!, + hintText: 'Insured Name', + // errorText: fieldErrors['name'], + borderColor: Colors.black, + isdense: true, + validator: (value) => Validators.requiredField(value, "name"), + widthNone: true, + ), + ); + } + + Widget buildVehicleType(BuildContext context) { + // Find the matching map from your list + + Map? selectedVehicle = filteredVechicleData.firstWhere( + (item) => item['id'].toString() == selectedVehicleType, + orElse: () => {}, + ); + + return SizedBox( + width: MediaQuery.of(context).size.width * 0.07, + height: 30, + child: DropdownSearch>( + key: dropDownKey, + selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, + items: (filter, infiniteScrollProps) { + return filteredVechicleData; + }, + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + + itemAsString: (val) => val['vehicle_type'].toString(), + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null + ? selectedItem['vehicle_type'].toString() + : "", // ✅ FIXED + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Vehicle Type", + ).copyWith( + filled: true, + fillColor: Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide(color: Colors.black, width: 0.1), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 200), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + padding: const EdgeInsets.all(1.0), + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Vehicle Type...", + hintStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black), + labelStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), // 👈 Normal border + ), + isDense: true, // reduces height + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 8, // reduce this value for smaller height + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + child: Text( + item['vehicle_type'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected vehicle_type : ${val['vehicle_type']}"); + print("Id: ${val['id']}"); + selectedVehicleType = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ); + } + + Widget buildEmail(BuildContext context) { + return ThemedFormInlineField( + controller: controllers['email']!, + borderColor: Colors.black, + isdense: true, + validator: (value) => Validators.nonReqemail(value, "email"), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')), + ], + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.26, + ); + } + + Widget buildPhNumber(BuildContext context) { + return ThemedFormInlineField( + controller: controllers['mobile']!, + borderColor: Colors.black, + isdense: true, + validator: (value) => Validators.nonReqphone(value, "phone"), + inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[ 0-9]'))], + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.26, + ); + } + + Widget buildRemarks(BuildContext context) { + return ThemedFormField( + // maxLength: 500, + enableBorderWidth: 0.1, + controller: controllers['remarks']!, + borderColor: Colors.black, + verticalPad: 10, + horizonalPad: 10, + isdense: true, + // keyboardType: TextInputType.multiline, + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.1, + ); + } + + Widget buildResponsiveUploadField({ + required String label, + required String? hintText, + required double? width, + required void Function(String fileName, dynamic file) onFileSelected, + bool showDownload = false, + VoidCallback? onRemove, + VoidCallback? onDownload, + }) { + final isMobile = ResponsiveLayout.isMobile(context); + + final uploadWidget = ThemedUploadField( + hintText: hintText ?? "Upload Document", + txtwidth: width, + padVertical: 6, + padHorizontal: 8, + + backgroundColor: Color(0xffD9EBE8), + txtName: label, + isTxtBtnCase: true, + onFileSelected: onFileSelected, + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [uploadWidget], + ), + ], + ); + } + + Widget buildUploadRCDocument(BuildContext context) { + return buildResponsiveUploadField( + label: "RC", + width: 60, + hintText: selectedRCFile, + onFileSelected: (fileName, file) { + setState(() { + docUploadedRCFile = file; + selectedRCFileName = fileName; + }); + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: docUploadedRCFile != null || rcFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedRCFile = null; + rcFileUrlFromApi = null; + selectedRCFileName = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc', + + apiId: selectedId.toString(), + + localFile: docUploadedRCFile, + fileName: 'RC', + ), + ); + } + + Widget buildUploadIDDocument(BuildContext context) { + return buildResponsiveUploadField( + label: "ID Proof", + width: 80, + hintText: selectedIdProof, + onFileSelected: (fileName, file) { + setState(() { + docUploadedIDProof = file; + // selectedFileNames = fileName; + }); + + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: docUploadedIDProof != null || idProofFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedIDProof = null; + idProofFileUrlFromApi = null; + // selectedFileNames = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof', + + apiId: selectedId.toString(), + localFile: docUploadedIDProof, + fileName: 'Id_Proof', + ), + ); + } + + Widget buildUploadPolicyDocument(BuildContext context) { + return buildResponsiveUploadField( + width: 100, + label: "PrevPolicy", + hintText: selectedPrevPolicy, + onFileSelected: (fileName, file) { + setState(() { + docUploadedPrevPolicy = file; + // selectedFileNames = fileName; + }); + ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); + }, + showDownload: + docUploadedPrevPolicy != null || prevPolicyFileUrlFromApi != null, + onRemove: () { + setState(() { + docUploadedPrevPolicy = null; + prevPolicyFileUrlFromApi = null; + // selectedFileNames = null; + }); + }, + onDownload: () => apiService.downloadFile( + // apiUrl: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy', + + apiId: selectedId.toString(), + localFile: docUploadedPrevPolicy, + fileName: 'Previous_Policy', + ), + ); + } + + Widget buildDocRC(BuildContext context, selectedEnquiryId) { + return InkWell( + onTap: () { + print("Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc'; + print("Uploadpath $path"); + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Tooltip( + message: 'Download RC', + child: Text( + "RC", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ); + } + + Widget buildDocIdProof(BuildContext context, selectedEnquiryId) { + return InkWell( + onTap: () { + print("Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof'; + print("Uploadpath $path"); + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Tooltip( + message: 'Download ID Proof', + child: Text( + "ID Proof", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ); + } + + Widget buildDocPrevPolicy(BuildContext context, selectedEnquiryId) { + return InkWell( + onTap: () { + print("QD597 Upload $selectedEnquiryId"); + final selectedId = selectedEnquiryId; + // final path = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy'; + + print("Uploadpath $path"); + apiService.getPdfDownload(path, selectedId); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0xFF425B5B), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Tooltip( + message: 'Download Previous Policy', + child: Text( + "Prev Policy", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ); + } + + // Update validation method to set errors + bool _validateRequiredFields() { + setState(() { + fieldErrors.clear(); // Clear previous errors + + // Validate Partner/Agent + if (selectedAgent == null || selectedAgent == '') { + fieldErrors['agent'] = 'Partner is required'; + } + + // Validate Insured Name + if (controllers['name']?.text.trim().isEmpty ?? true) { + fieldErrors['name'] = 'Insured Name is required'; + } + + // Validate Vehicle Number + if (controllers['regNo']?.text.trim().isEmpty ?? true) { + fieldErrors['regNo'] = 'Vehicle Number is required'; + } + + // Validate Vehicle Type + if (selectedVehicleType == null || selectedVehicleType == '') { + fieldErrors['vehicleType'] = 'Vehicle Type is required'; + } + + // Validate email format if provided + final email = controllers['email']?.text.trim() ?? ''; + if (email.isNotEmpty) { + final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); + if (!emailRegex.hasMatch(email)) { + fieldErrors['email'] = 'Invalid email format'; + } + } + + // Validate mobile number if provided + final mobile = controllers['mobile']?.text.trim() ?? ''; + if (mobile.isNotEmpty) { + final cleanMobile = mobile.replaceAll(' ', ''); + if (cleanMobile.length < 10) { + fieldErrors['mobile'] = 'Must be at least 10 digits'; + } + } + }); + + return true; + + return fieldErrors.isEmpty; + } + + Future handleEnqTabSave() async { + if (!_validateRequiredFields()) return; + + // setState(() { + // isSaving = true; // start saving + // }); + + final dataSet = dataDetails(); + + try { + await createUserData(dataSet); // API call + // ToastHelper.showInfoToast(context, "Enquiry saved successfully"); + } catch (e) { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('Error'), + content: Text(e.toString()), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + } finally { + // setState(() => isSaving = false); // re-enable button after save + } + } + + Future attachFiles(http.MultipartRequest request) async { + Future addFileOrKeepName( + PlatformFile? file, + String? apiFileName, + String fieldName, + ) async { + if (file != null) { + // User uploaded a new file → send as multipart + if (file.bytes != null) { + request.files.add( + http.MultipartFile.fromBytes( + fieldName, + file.bytes!, + filename: file.name, + ), + ); + } else if (file.path != null) { + request.files.add( + await http.MultipartFile.fromPath( + fieldName, + file.path!, + filename: file.name, + ), + ); + } + print("📎 Attached new file → $fieldName"); + } else if (apiFileName != null && apiFileName.isNotEmpty) { + // No new upload → tell backend to keep old file + request.fields[fieldName] = apiFileName; + print("🔗 Kept old file → $fieldName = $apiFileName"); + } else { + // Nothing at all + request.fields[fieldName] = ""; + } + } + + await addFileOrKeepName( + docUploadedRCFile, + rcFileUrlFromApi, + 'rc_file_name', + ); + await addFileOrKeepName( + docUploadedIDProof, + idProofFileUrlFromApi, + 'id_proof_file_name', + ); + await addFileOrKeepName( + docUploadedPrevPolicy, + prevPolicyFileUrlFromApi, + 'previous_policy_file_name', + ); + } + + Future createUserData(Map userData) async { + final bool isNewRow = + idPrimary != null && + int.tryParse(idPrimary.toString()) != null && + int.parse(idPrimary.toString()) > 1000000000000; + + final bool isUpdating = + idPrimary != null && idPrimary != 'null' && !isNewRow; + + print('isNewRow: $isNewRow, isUpdating: $isUpdating'); + + // final bool isUpdating = idPrimary != 'null'; + final id = idPrimary; + print('UPDId- $id'); + + // final bool isUpdating = false; + final uri = Uri.parse( + isUpdating + ? '${Env.apiUrl}enquiry/updateEnquiry' + : '${Env.apiUrl}enquiry/createEnquiry', + ); + if (_token == null) { + throw Exception('Token not found. Please log in.'); + } + + // Use MultipartRequest (POST only) + final request = http.MultipartRequest('POST', uri); + request.headers['Authorization'] = 'Bearer $_token'; + request.headers['app-signature'] = Env.App_Signature; + + // If updating, spoof the method Laravel-style + if (isUpdating) { + request.fields['id'] = id!; + request.fields['updated_by'] = userId!.toString(); + } else { + request.fields['created_by'] = userId!.toString(); + } + + print("USerDAta - $userData"); + + // userData.forEach((key, value) { + // request.fields[key] = value.toString(); + // print("✅ Encoded travel_details2: ${request.fields[key]}"); + // }); + await attachFiles(request); + userData.forEach((key, value) { + if (key != 'rc_file_name' && key != 'id_proof_file_name') { + request.fields[key] = value.toString(); + print("✅ Encoded $key: ${request.fields[key]}"); + } else { + print('Something Missing..'); + } + }); + + // attach files + + // request.fields['agent_id'] = selectedId.toString(); + + print(" Sending request with fields: ${request.fields}"); + + try { + final streamedResponse = await request.send(); + final response = await http.Response.fromStream(streamedResponse); + print("Response status: ${response.statusCode}"); + print("Response body: ${response.body}"); + + if (response.statusCode == 200 || response.statusCode == 201) { + // dispose(); + print("✅ Enquiry submitted successfully!"); + ToastHelper.showSuccessToast(context, 'Saved Enquiry'); + print("Response: ${response.body}"); + // Clear the temporary ID + idPrimary = null; + refrshfilterDateRange(); + // getStaffList(managerId, roleId); + // setState(() => isSaving = false); + } else if (response.statusCode == 403) { + await apiService.clearLocalStorageAndRedirect(); + } else { + print("❌ Submission failed. Status: ${response.statusCode}"); + print("Body: ${response.body}"); + + // setState(() => isSaving = false); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Enquiry Creation Failed"), + content: Text( + "There was a problem in creating enquiry. Please try again.", + ), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + } catch (e) { + print("🔥 Error submitting user: $e"); + } + } + + Widget buildSave(context) { + return ElevatedButton(onPressed: () {}, child: Text('Save')); + } + + //---------------------- Enquiry TAB Logins End -------------------------------- // + @override + Widget build(BuildContext context) { + return MainLayout( + title: "Enquiries", + body: Container( + // color: Colors.yellow.shade50, + width: MediaQuery.of(context).size.width, + padding: EdgeInsets.all(3.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + 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), + onPressed: () async { + context.go(AppRoutes.dashboard); + final prefs = + await SharedPreferences.getInstance(); + await prefs.remove('dashboardKeyProvider'); + await prefs.remove('dashboardStatusProvider'); + await prefs.remove('dashboardStaffIdProvider'); + }, + splashRadius: 18, + padding: const EdgeInsets.all(4), + hoverColor: Colors.black12, + constraints: const BoxConstraints(), + ), + ), + const SizedBox( + width: 5, + ), // spacing between icon and text + Text( + "Enquiries", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + Row( + children: [ + buildAgentName(context, fromHeader: true), + SizedBox(width: 5), + buildInsuredName(context, fromHeader: true), + SizedBox(width: 5), + buildInsurer(context, fromHeader: true), + SizedBox(width: 5), + buildVehicleNumber(context), + SizedBox(width: 5), + buildSave(context), + ], + ), + ], + ), + + // SizedBox(height: 5), + ResponsiveLayout.isMobile(context) + ? Container( + height: MediaQuery.of(context).size.height * 0.69, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(2), + child: _buildContent(context), + ), + ), + ) + : Expanded( + child: Container( + width: MediaQuery.of(context).size.width, + // padding: EdgeInsets.symmetric( + // horizontal: 8.0, + // vertical: 2.0, + // ), + child: _buildContent(context), + ), + ), + + 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; + }); + }, + ), + ), + ], + ), + ), + ); + } + + Future buildStatusActions(BuildContext context, status, id) async { + print("Actionsstatus - $status -$id"); + dynamic val; + if (status == 'Awaiting Proposal' || status == 'Proposal Created') { + val = ''; + } else { + val = 'Policy'; + } + + final result = await showDialog( + context: context, + barrierDismissible: + false, // optional - prevents closing by tapping outside + builder: (context) => TabEnquiryStaffList(showKey: val), + ); + + // ✅ Code here runs *after* the dialog is closed + print("Dialog closed"); + print("Dialog result: $result"); + refrshfilterDateRange(); + } + + Widget _buildContent(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + // height: 40, + // color: Colors.pink, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ThemedSearchField( + hintText: 'Search', + backgroundColor: Color(0xFFF6F8F8), + onChanged: filterData, + txtHeight: 35, + controller: _searchStaffController, + txtwidth: ResponsiveLayout.isMobile(context) + ? MediaQuery.of(context).size.width * 0.7 + : MediaQuery.of(context).size.width * 0.13, + ), + IconButton( + icon: Icon( + _showFilterRow + ? Icons.close_fullscreen + : Icons.filter_alt_outlined, + ), + iconSize: 18, + tooltip: _showFilterRow ? 'Hide Filters' : 'Show Filters', + onPressed: () { + setState(() { + _showFilterRow = !_showFilterRow; + }); + }, + ), + if (_showFilterRow) ...[ + DateFilterRow( + key: ValueKey(SelectedStatus ?? ''), + role: roleId, + id: userId, + selectedStatusVal: SelectedStatus, + onFilterStaff: (val) { + print('Selected Filterd STAFF Id - $val'); + SelectedStaffId = val; + }, + selectedStaffId: SelectedStaffId, + startController: controllers['startDate']!, + endController: controllers['endDate']!, + onStatusChanged: (val) { + SelectedStatus = val; // update parent + }, + formKey: _formKey, + isMobile: ResponsiveLayout.isMobile(context), + onFilter: () { + // call your filter logic + filterDateRange(); + }, + onRefresh: () { + // call your refresh logic + refrshfilterDateRange(); + }, + ), + ], + + Spacer(), + + // ResponsiveLayout.isMobile(context) + // ? Spacer() + // : + SizedBox(width: 10), + + ExportBtn( + sheetName: "Enquiry", + fileName: "Enquiry_list", + data: filteredData, + txt: !ResponsiveLayout.isMobile(context) ? true : false, + displayHeaders: [ + "Received Date & Time", + "Partner", + "Assigned To", + "Insurer", + "Vehicle.No.", + "Insured Name", + "Assigned Date & Time", + "Premium", + "Payment Mode", + "Policy Number", + "Status", + ], + keys: [ + "created_on", + "agent_name", + "assigned_to_name", + "insurer_name", + "reg_no", + "insured_name", + "updated_on", + "premium_amount", + "payment_mode", + "policy_number", + "status", + ], + ), + if (roleId != 'staff') ...[ + SizedBox(width: 10), + GestureDetector( + onTap: () async { + // final prefs = await SharedPreferences.getInstance(); + // + // await prefs.remove('enqAgentDataId'); + // ref.read(enquiryIdProvider.notifier).state = null; + // context.go(AppRoutes.tabEnquiry); + // print('Export'); + + // Generate a unique ID for the new row + final newId = DateTime.now().millisecondsSinceEpoch; + + // Clear all existing form controllers + for (String field in tabHeader) { + controllers[field]?.clear(); + } + + // Reset dropdown selections + selectedVehicleType = null; + selectedAgent = null; + selectedInsurer = null; + + // Reset file uploads + docUploadedRCFile = null; + docUploadedIDProof = null; + docUploadedPrevPolicy = null; + selectedRCFile = null; + selectedIdProof = null; + selectedPrevPolicy = null; + + setState(() { + // Create a new empty row data + final newRow = { + 'id': newId + .toString(), // Keep as String for consistency + 'created_on': '', + 'agent_name': '', + 'agent_id': '', + 'assigned_to_name': '', + 'insurer_name': '', + 'insured_name': '', + 'reg_no': '', + 'vehicle_type': '', + 'vehicle_type_id': '', + 'email': '', + 'mobile': '', + 'remarks': '', + 'updated_on': '', + 'premium_amount': '', + 'payment_mode': '', + 'policy_number': '', + 'status': '', + }; + + // Insert at the beginning of ALL lists + getStaffData.insert(0, newRow); + originalData.insert(0, newRow); + filteredData.insert(0, newRow); + + // Set to editing mode immediately + isEditingRow[newId] = true; + + // Store the ID as string + idPrimary = newId.toString(); + selectedId = newId.toString(); + + // Reset to first page to see the new row + currentPage = 1; + }); + }, + child: Container( + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: Color(0xFF425B5B), + borderRadius: BorderRadius.circular(8.0), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Tooltip( + message: 'Raise Enquiry', + child: Icon(Icons.add, color: Colors.white, size: 15), + ), + ], + ), + ), + ), + ], + ], + ), + ), + // SizedBox(height: 10), + + // Container( + // decoration: BoxDecoration( + // color: const Color(0xFFEDF6F5), + // borderRadius: BorderRadius.circular(6), + // ), + // padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + // child: Row( + // children: const [ + // Expanded(flex: 1, child: Text('S.No', style: _headerStyle)), + // Expanded( + // flex: 2, + // child: Text('Received Date', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Partner *', style: _headerStyle)), + // Expanded( + // flex: 2, + // child: Text('Assigned To *', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Broker *', style: _headerStyle)), + // Expanded(flex: 3, child: Text('Insurer *', style: _headerStyle)), + // Expanded( + // flex: 3, + // child: Text('Insured Name *', style: _headerStyle), + // ), + // Expanded( + // flex: 2, + // child: Text('Vehicle No *', style: _headerStyle), + // ), + // Expanded( + // flex: 2, + // child: Text('Vehicle Type *', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Email', style: _headerStyle)), + // Expanded(flex: 2, child: Text('Mobile', style: _headerStyle)), + // Expanded(flex: 2, child: Text('Documents', style: _headerStyle)), + // Expanded(flex: 2, child: Text('Remarks', style: _headerStyle)), + // Expanded( + // flex: 2, + // child: Text('Assigned Date', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Premium', style: _headerStyle)), + // Expanded( + // flex: 2, + // child: Text('Payment Mode', style: _headerStyle), + // ), + // Expanded( + // flex: 2, + // child: Text('Policy Number', style: _headerStyle), + // ), + // Expanded(flex: 2, child: Text('Status', style: _headerStyle)), + // Expanded(flex: 1, child: Text('Action', style: _headerStyle)), + // ], + // ), + // ), + ResponsiveLayout.isMobile(context) + ? _buildDataTable(context) + : Expanded(child: _buildDataTable(context)), + ], + ); + } + + List _buildPopupDownload(BuildContext context, selectId, editing) { + bool isMobile = ResponsiveLayout.isMobile(context); + return [ + editing + ? Container( + // color: Colors.red.shade50, + padding: EdgeInsets.only(top: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + + children: [ + buildUploadRCDocument(context), + SizedBox(width: 3), + buildUploadIDDocument(context), + SizedBox(width: 3), + buildUploadPolicyDocument(context), + ], + ), + ) + : Row( + children: [ + buildDocRC(context, selectId), + SizedBox(width: 3), + buildDocIdProof(context, selectId), + SizedBox(width: 3), + buildDocPrevPolicy(context, selectId), + ], + ), + ]; + } + // Add this to your EnquiryStaffState class + + // 1. Add group expansion state tracking + Map groupExpanded = { + 'Awaiting Proposal': true, + 'Proposal Created': true, + 'Proposal Accepted': true, + 'Proposal Rejected': true, + 'Policy Created': true, + }; + + // 2. Add method to group enquiries by status + Map>> get _groupedEnquiries { + final grouped = >>{}; + + // Initialize all status groups + grouped['Awaiting Proposal'] = []; + grouped['Proposal Created'] = []; + grouped['Proposal Accepted'] = []; + grouped['Proposal Rejected'] = []; + grouped['Policy Created'] = []; + + // Group the filtered/paginated data by status + for (var item in _paginatedData) { + final status = item['status']?.toString() ?? 'Unknown'; + if (grouped.containsKey(status)) { + grouped[status]!.add(item); + } + } + + return grouped; + } + + // 3. Add method to get status icon and color + Map _getStatusConfig(String status) { + switch (status) { + case 'Awaiting Proposal': + return {'icon': '🕐', 'color': Color(0xFFfff3cd)}; + case 'Proposal Created': + return {'icon': '📝', 'color': Color(0xFFd1ecf1)}; + case 'Proposal Accepted': + return {'icon': '✅', 'color': Color(0xFFd4edda)}; + case 'Proposal Rejected': + return {'icon': '❌', 'color': Color(0xFFf8d7da)}; + case 'Policy Created': + return {'icon': '📋', 'color': Color(0xFFcfe2ff)}; + default: + return {'icon': '📄', 'color': Color(0xFFe9ecef)}; + } + } + + // 4. Build group header widget + Widget _buildGroupHeader(String status, int count) { + final config = _getStatusConfig(status); + final isExpanded = groupExpanded[status] ?? true; + + return InkWell( + onTap: () { + setState(() { + groupExpanded[status] = !isExpanded; + }); + }, + child: Container( + color: Color(0xFFf8f9fa), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + children: [ + Icon( + isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, + size: 24, + ), + SizedBox(width: 8), + Text( + '${config['icon']} $status', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + Spacer(), + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: Color(0xFFe3f2fd), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '$count Records', + style: GoogleFonts.inter( + fontSize: 11, + color: Color(0xFF1976d2), + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildDataTable(BuildContext context) { + if (filteredData.isEmpty) { + return const SizedBox( + height: 50, + child: Center(child: Text('No available data')), + ); + } + + final sortedData = [..._paginatedData]; + final startIndex = (currentPage - 1) * itemsPerPage; + final isDesktop = !ResponsiveLayout.isMobile(context); + + // Build both-direction scrollable DataTable + return LayoutBuilder( + builder: (context, constraints) { + // double minWidth = isDesktop ? constraints.maxWidth : 1300; + double minWidth = constraints.maxWidth < 1300 + ? 1300 + : constraints.maxWidth; + return ScrollConfiguration( + behavior: const MaterialScrollBehavior().copyWith( + dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch}, + ), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth), + child: DataTable( + headingRowColor: WidgetStatePropertyAll(Color(0xFFEDF6F5)), + dividerThickness: 0.5, + headingRowHeight: 30, + columnSpacing: isDesktop ? 20.0 : 16.0, + border: TableBorder( + horizontalInside: BorderSide( + width: 0.5, + color: Colors.grey.shade200, + ), + ), + columns: [ + // DataColumn(label: Text('S.No', style: _headerStyle)), + const DataColumn( + label: Text('Received Date', style: _headerStyle), + ), + const DataColumn( + label: Text('Partner *', style: _headerStyle), + ), + const DataColumn( + label: Text('Assigned To *', style: _headerStyle), + ), + const DataColumn( + label: Text('Broker *', style: _headerStyle), + ), + const DataColumn( + label: Text('Insurer *', style: _headerStyle), + ), + const DataColumn( + label: Text('Insured Name *', style: _headerStyle), + ), + const DataColumn( + label: Text('Vehicle No *', style: _headerStyle), + ), + const DataColumn( + label: Text('Vehicle Type *', style: _headerStyle), + ), + const DataColumn(label: Text('Email', style: _headerStyle)), + const DataColumn( + label: Text('Mobile', style: _headerStyle), + ), + const DataColumn( + label: Text('Documents', style: _headerStyle), + ), + const DataColumn( + label: Text('Remarks', style: _headerStyle), + ), + if (roleId != 'staff') ...const [ + DataColumn(label: Text('Action', style: _headerStyle)), + ], + const DataColumn( + label: Text('Assigned Date', style: _headerStyle), + ), + const DataColumn( + label: Text('Premium', style: _headerStyle), + ), + const DataColumn( + label: Text('Payment Mode', style: _headerStyle), + ), + const DataColumn( + label: Text('Policy Number', style: _headerStyle), + ), + const DataColumn( + label: Text('Status', style: _headerStyle), + ), + ], + rows: sortedData.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + final sno = startIndex + index + 1; + return _buildDataRow(item, sno); + }).toList(), + ), + ), + ), + ), + ); + }, + ); + } + + DataRow _buildDataRow(Map item, int sno) { + final id = int.tryParse(item['id'].toString()) ?? 0; + final selectId = item['id'].toString() ?? '0'; + + final bool isNewRow = + int.tryParse(id.toString()) != null && + int.parse(id.toString()) > 1000000000000; + + print('idPrimary - $idPrimary'); + print('isNewRow - $isNewRow'); + final editing = isEditingRow[id] ?? false; + final controllers = rowControllers[id]; + + return DataRow( + cells: [ + // S.No. + // DataCell(Text('$sno', style: _dataBold)), + + // Created On + DataCell( + Row( + children: [ + // Text(item['created_on'], style: _dataBold), + Text( + formatDateTimeForTable(item['created_on'], newLine: true), + style: _dataBold, + ), + ], + ), + ), + + // Agent Name + DataCell( + editing + ? buildAgentName(context) + : Text(item['agent_name'] ?? '-', style: _dataBold), + ), + + // Assigned To + DataCell( + editing + ? buildSelectStaffMem(context) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(item['agent_code'] ?? '-', style: _dataBold), + Text( + item['assigned_to_name'] != null + ? _truncateText(item['assigned_to_name'], 10) + : '-', + style: _tableDataTimeStyle, + ), + ], + ), + ), + + DataCell( + editing + ? buildBroker(context) + : Text(item['broker_name'] ?? '-', style: _dataBold), + ), + + // Insurer Name + DataCell( + editing + ? buildInsurer(context) + : Text(item['insurer_short_name'] ?? '-', style: _dataBold), + ), + + DataCell( + editing + ? buildInsuredName(context) + : Text(item['insured_name'] ?? '-', style: _dataBold), + ), + // Vehicle No + DataCell( + editing + ? buildVehicleNumber(context) + : Text(item['reg_no'] ?? '-', style: _dataBold), + ), + DataCell( + editing + ? buildVehicleType(context) + : Text( + _wrapText(item['vehicle_type'] ?? '-', 13), + style: _dataBold, + softWrap: true, + ), + ), + + DataCell( + editing + ? buildEmail(context) + : Text(item['email'] ?? '-', style: _dataBold), + ), + DataCell( + editing + ? buildPhNumber(context) + : Text(item['mobile'] ?? '-', style: _dataBold), + ), + + DataCell( + Builder( + builder: (buttonContext) => Material( + color: Colors.white, + child: InkWell( + hoverColor: const Color(0xFFEAF6F4), + onTap: () async { + // ✅ Get overlay and button position RELATIVE to InkWell + final RenderBox button = + buttonContext.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(buttonContext).context.findRenderObject() + as RenderBox; + + final Offset position = button.localToGlobal( + Offset.zero, + ancestor: overlay, + ); + + await showMenu( + context: buttonContext, + position: RelativeRect.fromLTRB( + position.dx, + position.dy + button.size.height, + overlay.size.width, + 0, + ), + items: [ + PopupMenuItem( + child: Column( + mainAxisSize: MainAxisSize.min, + children: _buildPopupDownload( + buttonContext, + selectId, + editing, + ), + ), + ), + ], + color: Colors.white, + ); + }, + child: Tooltip( + message: editing + ? 'Click To Upload Documents' + : 'Click To View Documents', + waitDuration: const Duration(milliseconds: 500), + showDuration: const Duration(seconds: 2), + child: Icon( + editing ? Icons.upload_file : Icons.download_for_offline, + color: editing ? Colors.blue : Colors.green, + size: 22, + ), + ), + ), + ), + ), + ), + + DataCell( + editing + ? buildRemarks(context) + : Tooltip( + message: item['remarks'] ?? '-', // full content on hover + waitDuration: const Duration(milliseconds: 400), + showDuration: const Duration(seconds: 2), + child: Text( + _truncateText(item['remarks'] ?? '-', 10), + style: _dataBold, + overflow: TextOverflow.ellipsis, + ), + ), + ), + + // Actions + if (roleId != 'staff') ...[ + DataCell( + // (editing && (item['status'] == 'Awaiting Proposal')) + editing + ? Row( + children: [ + IconButton( + icon: const Icon(Icons.check, color: Colors.green), + tooltip: "Save", + onPressed: () => handleSave(id), + ), + + if (isNewRow) ...[ + IconButton( + icon: const Icon( + Icons.close, + color: Colors.red, + size: 18, + ), + tooltip: "Delete", + onPressed: () => handleCancel(id), + ), + ], + if (!isNewRow) ...[ + IconButton( + icon: const Icon( + Icons.edit_off_sharp, + color: Colors.blue, + size: 18, + ), + tooltip: "Cancel Edit Mode", + onPressed: () => handleCancel(id), + ), + ], + ], + ) + : Row( + children: [ + IconButton( + icon: const Icon( + Icons.edit, + color: Colors.blue, + size: 18, + ), + + tooltip: "Edit", + onPressed: () { + handleEdit(item); + }, + ), + ], + ), + ), + ], + // Assigned Date (formatted) + DataCell( + Text( + // item['updated_on'] ?? '-', + formatDateTimeForTable(item['updated_on'], newLine: true), + style: _dataBold, + softWrap: true, + maxLines: 3, + ), + ), + + // Premium + DataCell( + // ((item['premium_amount'] != null) && + // (item['premium_amount'] != '') && + // ((item['status'] != 'Awaiting Proposal') || + // (item['status'] == 'Proposal Created') || + // (item['status'] == 'Proposal Rejected'))) + (item['status'] == 'Proposal Created' || + item['status'] == 'Proposal Rejected') + ? InkWell( + onTap: () { + // handle approval logic + + handleProposalAccept(context, item['id']); + }, + child: Text( + 'Click To\nApprove', + style: GoogleFonts.inter( + color: Colors.green, + fontSize: 11, + fontWeight: FontWeight.w700, + ), + ), + ) + : Text( + item['premium_amount']?.toString() ?? '-', + style: _dataBold, + ), + ), + + // Payment Mode + DataCell(Text(item['payment_mode'] ?? '-', style: _dataBold)), + + // Policy Number + DataCell( + Text( + item['policy_number'] ?? '-', + style: _dataBold, + softWrap: true, + maxLines: 3, + ), + ), + + // Status + // DataCell(Text(item['status'] ?? '-', style: _dataBold)), + DataCell( + Material( + color: Colors.white, + child: InkWell( + hoverColor: Color(0xFFEAF6F4), + onTap: () async { + var status = item['status']; + var enqId = item['id']; + + final prefs = await SharedPreferences.getInstance(); + + // ✅ Remove old value (if any) + await prefs.remove('enqStaffDataId'); + + // ✅ Save the new id + await prefs.setString('enqStaffDataId', enqId.toString()); + ref.read(quotationStaffIdProvider.notifier).state = enqId; + buildStatusActions(context, status, enqId); + }, + child: Tooltip( + message: 'Click to View or Process Enquiry', // + waitDuration: const Duration(milliseconds: 500), // optional + showDuration: const Duration(seconds: 2), // optional + child: Text( + item['status'] ?? '-', + style: _dataBold, + overflow: TextOverflow.ellipsis, // + ), + ), + // child: Text(item['status'] ?? '-', style: _dataBold), + ), + ), + ), + ], + ); + } + + static final _dataBold = TextStyle( + fontSize: 12, + + fontWeight: FontWeight.w500, + color: Colors.black, + // color: Color(0xFF000000), + ); + final _tableDataTimeStyle = GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w400, + ); + static final _dataBoldsmall = TextStyle( + fontSize: 12, + + fontWeight: FontWeight.w400, + color: Color(0xFF000000), + ); + + static final _dataSub = TextStyle( + fontSize: 10, + fontWeight: FontWeight.w300, + color: Color(0xFF585757), + ); + + static const _headerStyle = TextStyle( + color: Colors.black, + fontWeight: FontWeight.w500, + ); + static const _cardheaderStyle = TextStyle( + color: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 12, + ); + + static final TextStyle _textStyle = TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + ); + + static const _cardBodyStyle = TextStyle( + color: Color(0xFF545454), + fontWeight: FontWeight.w400, + fontSize: 12, + ); + + String _truncateText(String text, int limit) { + if (text.length <= limit) return text; + return '${text.substring(0, limit)}...'; + } + + String _wrapText(String text, int chunkSize) { + if (text.isEmpty) return '-'; + final buffer = StringBuffer(); + for (int i = 0; i < text.length; i += chunkSize) { + int end = (i + chunkSize < text.length) ? i + chunkSize : text.length; + buffer.writeln(text.substring(i, end)); // adds '\n' after each chunk + } + return buffer.toString().trim(); + } + + String formatDateTimeForTable(String? input, {bool newLine = false}) { + if (input == null || input.trim().isEmpty) return '-'; + + final s = input.trim(); + DateTime dt; + + try { + // Case 1: ISO-like (yyyy-MM-dd ...), DateTime.parse will handle + if (RegExp(r'^\d{4}-\d{2}-\d{2}').hasMatch(s)) { + dt = DateTime.parse(s); + } + // Case 2: day-month-year at start (e.g. 13-11-2025 07:28 AM or 13-11-2025 07:28) + else if (RegExp(r'^\d{2}-\d{2}-\d{4}').hasMatch(s)) { + // Try with am/pm (hh:mm a) + if (RegExp(r'\b(am|pm)\b', caseSensitive: false).hasMatch(s)) { + dt = DateFormat('dd-MM-yyyy hh:mm a').parse(s); + } + // Try 24-hour time "dd-MM-yyyy HH:mm" or just date "dd-MM-yyyy" + else if (RegExp(r'^\d{2}-\d{2}-\d{4}\s+\d{1,2}:\d{2}').hasMatch(s)) { + dt = DateFormat('dd-MM-yyyy HH:mm').parse(s); + } else { + dt = DateFormat('dd-MM-yyyy').parse(s); + } + } + // Case 3: fallback — try parse with DateTime.parse (may throw) + else { + dt = DateTime.parse(s); + } + + final dateStr = DateFormat('dd-MM-yyyy').format(dt); + final timeStr = DateFormat('hh:mm a').format(dt); // e.g. 07:28 AM + + return newLine ? '$dateStr\n$timeStr' : '$dateStr | $timeStr'; + } catch (e) { + // If parsing fails, return original string as fallback (or '-' if empty) + return s.isNotEmpty ? s : '-'; + } + } +} diff --git a/lib/presentation/screens/handler/quotationPopUp.dart b/lib/presentation/screens/staff/Enquiry/quotationPopUpAccptReject.dart similarity index 98% rename from lib/presentation/screens/handler/quotationPopUp.dart rename to lib/presentation/screens/staff/Enquiry/quotationPopUpAccptReject.dart index 6fa0d16..1c6b00f 100644 --- a/lib/presentation/screens/handler/quotationPopUp.dart +++ b/lib/presentation/screens/staff/Enquiry/quotationPopUpAccptReject.dart @@ -4,12 +4,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import 'package:nhance_partner/presentation/providers/userRoleProvider.dart'; -import '../../../../core/config/env.dart'; -import '../../../../core/services/api_service.dart'; -import '../../../../data/services/auth_service.dart'; -import '../../../../data/utils/toastNotification.dart'; -import '../../layouts/responsive_layout.dart'; -import '../../providers/manager_provider.dart'; +import '../../../../../core/config/env.dart'; +import '../../../../../core/services/api_service.dart'; +import '../../../../../data/services/auth_service.dart'; +import '../../../../../data/utils/toastNotification.dart'; +import '../../../layouts/responsive_layout.dart'; +import '../../../providers/manager_provider.dart'; class QuotationPopUpTab extends ConsumerStatefulWidget { String? id; @@ -168,7 +168,7 @@ class QuotationTabState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - 'Quotation Details', + 'Proposal Details', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), diff --git a/lib/presentation/screens/staff/Enquiry/raise_enq_form.dart b/lib/presentation/screens/staff/Enquiry/raise_enq_form.dart new file mode 100644 index 0000000..3c876f5 --- /dev/null +++ b/lib/presentation/screens/staff/Enquiry/raise_enq_form.dart @@ -0,0 +1,933 @@ +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../../core/services/api_service.dart'; +import '../../../../data/services/auth_service.dart'; +import '../../../../data/utils/validators.dart'; +import '../../../providers/manager_provider.dart'; +import '../../../providers/userRoleProvider.dart'; +import '../../../themes/indicators/input_field_decoration.dart'; +import '../../../themes/indicators/text_field_theme_inline_editor.dart'; + +class RaiseEnqForm extends ConsumerStatefulWidget { + final Function(Map data) onSubmit; + const RaiseEnqForm({super.key, required this.onSubmit}); + @override + ConsumerState createState() => RaiseEnqFormState(); +} + +class RaiseEnqFormState extends ConsumerState { + late ApiService apiService; + dynamic userId; + dynamic idPrimary; + dynamic managerId; + dynamic dashboardKey; + dynamic SelectedStatus; + dynamic SelectedStaffId; + dynamic handlerId; + String? _token; + final _formKey = GlobalKey(); + Map fieldErrors = {}; + + Map controllers = {}; + List tabHeader = [ + 'startDate', + 'endDate', + 'name', + 'email', + 'mobile', + 'code', + 'address', + 'regNo', + 'remarks', + ]; + + List> getVehicleTypeData = []; + List> filteredVechicleData = []; + + List> getAgentListData = []; + List> filteredAgentData = []; + + List> getInsurersData = []; + List> filteredInsurersData = []; + + final GlobalKey>> + dropDownKeyInsurerEnqAsgn = + GlobalKey>>(); + + final GlobalKey>> dropDownKeyBroker = + GlobalKey>>(); + final GlobalKey>> + dropDownSelectStaffKey = + GlobalKey>>(); + + final GlobalKey>> dropDownKey = + GlobalKey>>(); + final GlobalKey>> + dropDownKeyInsurer = GlobalKey>>(); + final GlobalKey>> dropDownKeyAgent = + GlobalKey>>(); + + List> getStaffDetailsDataEnqAsgn = []; + List> filteredStaffDataEnqAsgn = []; + + String? selectedStaff; + String? selectedRegNum; + String? selectedStaffName; + List> getBrokerData = []; + List> filteredBrokerData = []; + String? selectedBroker; + + String? selectedVehicleType; + String? selectedInsurer; + String? selectedAgent; + dynamic roleId; + + bool isLoading = false; + + Map dataDetails() { + final data = { + "agent_id": ((roleId == 'handler') || (roleId == 'manager')) + ? selectedAgent + : userId, + "name": controllers["name"]?.text, + "mobile": controllers["mobile"]?.text, + "email": controllers["email"]?.text, + "reg_no": controllers["regNo"]?.text, + "vehicle_type_id": selectedVehicleType, + "is_data_created_by_handler": roleId == 'handler' ? '1' : '0', + "is_data_created_by_manager": roleId == 'manager' ? '1' : '0', + // "insurer_id": selectedInsurer, + // "rc_file_name": "rc_doc.pdf", + // "id_proof_file_name": "id_proof.pdf", + // "previous_policy_file_name": "previous_policy.pdf", + "remarks": controllers["remarks"]?.text, + "assigned_to": (roleId == 'staff') ? userId : selectedStaff, + "insurer_id": selectedInsurer, + "broker_id": selectedBroker, + "manager_id": managerId, + "created_by": userId, + }; + + return data; + } + + @override + void initState() { + super.initState(); + + apiService = ApiService(); + + for (String field in tabHeader) { + controllers[field] = TextEditingController(); + } + _initializeToken(); + + Future.microtask(() async { + // final id = ref.read(managerIdProvider); + roleId = ref.read(userRoleProvider); + userId = ref.read(userIdProvider); + managerId = ref.read(managerIdProvider); + + final prefs = await SharedPreferences.getInstance(); + dashboardKey = prefs.getString('dashboardKeyProvider'); + final dashboardStatus = prefs.getString('dashboardStatusProvider'); + final dashboardStaffId = prefs.getString('dashboardStaffIdProvider'); + + print('handlerIdENQ - $handlerId'); + print("C72 => r : $roleId | uId: $userId !mID : $managerId "); + + print('dashboardKey - $dashboardKey'); + print('dashboardStatus - $dashboardStatus'); + print('dashboardStaffId - $dashboardStaffId'); + + if (managerId != null) { + print('managerId - $managerId'); + getAgentList(managerId); + } + + final userID = ref.watch(userIdProvider); + + print("managerId - $managerId"); + if (userID != null) { + print('hansles'); + getStaffDetailsForEnquiryAssignment(userID); + } + }); + + getVehicleType(); + getInsurers(); + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + print("APISERTOKEN - $_token"); + } + + void resetFormFields() { + // 1. Reset all text controllers + controllers.forEach((key, controller) { + controller.clear(); + }); + + // 2. Reset dropdown selections + setState(() { + selectedAgent = null; + selectedInsurer = null; + // Add any other dropdowns + }); + + // 3. Reset form validation + _formKey.currentState?.reset(); + } + + Future getVehicleType() async { + print('getClaimList called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('vehicleType'); + + if (response['status'] == 200) { + print('getVehicleTypeData - ${response['data']}'); + setState(() { + getVehicleTypeData = List>.from( + response['data'], + ); + print('API Data - $getVehicleTypeData'); + + filteredVechicleData = List.from(getVehicleTypeData); + print('originalData - $filteredVechicleData'); + }); + } else { + getVehicleTypeData = []; + filteredVechicleData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getInsurers() async { + print('Insurers called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('Insurers'); + + if (response['status'] == 200) { + print('getInsurers - ${response['data']}'); + setState(() { + getInsurersData = List>.from(response['data']); + print('API Data - $getInsurersData'); + + filteredInsurersData = List.from(getInsurersData); + print('originalData - $filteredInsurersData'); + }); + } else { + getInsurersData = []; + filteredInsurersData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getAgentList(id) async { + print('getAgentListData called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchAgentNameDropDown(id); + print('getAgentListData called response'); + print('get Agent- ${response['data']}'); + if (response['status'] == 'success') { + print('get Agent- ${response['data']}'); + setState(() { + getAgentListData = List>.from(response['data']); + print('API Data - $getAgentListData'); + + filteredAgentData = List.from(getAgentListData); + print('originalAgentData - $filteredAgentData'); + }); + } else { + getAgentListData = []; + filteredAgentData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getStaffDetailsForEnquiryAssignment(int id) async { + print('getStaffDetailsForEnquiryAssignment called By handler'); + setState(() { + isLoading = true; + }); + + try { + // final response = await apiService.fetchStaffUserList(id, role); + final response = await apiService.fetchStaffListForEnquiryAssignDropDown( + id, + roleId, + ); + + if (response['status'] == 'success') { + print('getStaffDetailsForEnquiryAssignment - ${response['data']}'); + setState(() { + getStaffDetailsDataEnqAsgn = List>.from( + response['data'], + ); + print('API Data - $getStaffDetailsDataEnqAsgn'); + + filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn); + print('originalData - $filteredStaffDataEnqAsgn'); + }); + } else { + getStaffDetailsDataEnqAsgn = []; + filteredStaffDataEnqAsgn = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + // TODO: implement build + return Container( + // color: Color(0xFFFCFCFC), + // margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 25), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 4, + spreadRadius: 1, + offset: const Offset(0, 2), // soft drop shadow + ), + ], + ), + child: Form( + key: _formKey, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + buildAgentName(context, fromHeader: true), + if (roleId != 'staff') ...[buildSelectStaffMem(context)], + buildInsurer(context, fromHeader: true), + buildInsuredName(context, fromHeader: true), + + buildVehicleNumber(context), + + buildSave(context), + ], + ), + ), + ); + } + + Widget buildAgentName(BuildContext context, {fromHeader = true}) { + Map? selectedAgntName = filteredAgentData.firstWhere( + (item) => item['id'].toString() == selectedAgent, + orElse: () => {}, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Partner', style: _subLabelTimeStyle), + + SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.15, + // width: fromHeader + // ? MediaQuery.of(context).size.width * 0.11 + // : MediaQuery.of(context).size.width * 0.08, + height: 30, + child: DropdownSearch>( + key: dropDownKeyAgent, + selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null, + items: (filter, infiniteScrollProps) { + return filteredAgentData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null + ? selectedItem['name'].toString() + : "", // ✅ FIXED + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Partner Name", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE2E8F0), + width: 0.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE2E8F0), + width: 0.5, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Partner Name", + hintStyle: GoogleFonts.inter( + fontSize: 10, + color: Colors.grey, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + item['name'].toString(), + style: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + ), + Text( + item['agent_code'].toString(), + style: GoogleFonts.inter( + fontSize: 11, + color: Colors.grey, + ), + ), + ], + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected Partner : ${val['name']}"); + print("Id: ${val['id']}"); + selectedAgent = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + + Widget buildInsuredName(BuildContext context, {fromHeader = true}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Insured', style: _subLabelTimeStyle), + + SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.15, + + child: ThemedFormInlineField( + controller: controllers['name']!, + hintText: 'Insured Name', + // errorText: fieldErrors['name'], + borderColor: Color(0xFFE2E8F0), + isdense: true, + validator: (value) => Validators.requiredField(value, "name"), + widthNone: true, + ), + ), + ], + ); + } + + Widget buildInsurer(BuildContext context, {fromHeader = true}) { + Map? selectedInsurerd = filteredInsurersData.firstWhere( + (item) => item['id'].toString() == selectedInsurer, + orElse: () => {}, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Insurer', style: _subLabelTimeStyle), + + SizedBox(height: 5), + Container( + height: 30, + width: MediaQuery.of(context).size.width * 0.12, + child: DropdownSearch>( + key: dropDownKeyInsurerEnqAsgn, + selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, + items: (filter, infiniteScrollProps) { + return filteredInsurersData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Insurer", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE2E8F0), + width: 0.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE2E8F0), + width: 0.5, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Insurer...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + // constraints: BoxConstraints(), + ), + + onChanged: (val) { + if (val != null) { + print("Selected Insurer : ${val['name']}"); + print("Id: ${val['id']}"); + selectedInsurer = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + + Widget buildVehicleNumber(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Vehicle Number', style: _subLabelTimeStyle), + + SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.15, + child: ThemedFormInlineField( + controller: controllers['regNo']!, + hintText: 'Vehicle Number', + borderColor: Color(0xFFE2E8F0), + isdense: true, + inputFormatters: [ + UpperCaseTextFormatter(), // 👈 custom formatter for uppercase + FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')), + ], + validator: (value) => Validators.requiredVechileNum(value, "regNo"), + widthNone: true, + ), + ), + ], + ); + } + + Widget buildSelectStaffMem(ctx) { + Map? selectedVehicle; + try { + selectedVehicle = filteredStaffDataEnqAsgn.firstWhere( + (item) => item['id'].toString() == selectedStaff, + ); + } catch (e) { + selectedVehicle = null; // ✅ fallback + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Assigned To', style: _subLabelTimeStyle), + + SizedBox(height: 5), + SizedBox( + height: 30, + width: MediaQuery.of(context).size.width * 0.15, + child: DropdownSearch>( + key: dropDownSelectStaffKey, + + // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, + selectedItem: selectedVehicle, + items: (filter, infiniteScrollProps) { + return filteredStaffDataEnqAsgn; + }, + + itemAsString: (val) => val['name'].toString(), + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Staff Member", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE2E8F0), + width: 0.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE2E8F0), + width: 0.5, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Staff ...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + // constraints: BoxConstraints(), + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected Staff : ${val['name']}"); + print("Id: ${val['id']}"); + selectedStaffName = val['name']; + selectedStaff = val['id']; + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + + Widget buildSave(context) { + return InkWell( + onTap: () { + if (!_formKey.currentState!.validate()) return; + + final dataSet = dataDetails(); + + widget.onSubmit(dataSet); + resetFields(); + }, + child: Container( + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: const Color(0xFF2E7D6E), + borderRadius: BorderRadius.circular(5.0), + ), + child: Text('Create', style: GoogleFonts.inter(color: Colors.white)), + ), + ); + } + + void resetFields() { + print('Field ReSET'); + // 1. Reset all text controllers + controllers.forEach((key, controller) { + controller.clear(); + }); + + // 2. Reset dropdown selections + setState(() { + selectedAgent = null; + selectedInsurer = null; + selectedStaff = null; + selectedStaffName = null; + selectedVehicleType = null; + selectedRegNum = null; + selectedBroker = null; + }); + + // 3. Reset dropdown UI (important for DropdownSearch) + dropDownKeyAgent.currentState?.clear(); + dropDownKeyInsurerEnqAsgn.currentState?.clear(); + dropDownSelectStaffKey.currentState?.clear(); + dropDownKeyInsurer.currentState?.clear(); + dropDownKey.currentState?.clear(); + dropDownKeyBroker.currentState?.clear(); + + // 4. Reset validators + _formKey.currentState?.reset(); + } + + final _subLabelTimeStyle = GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Color(0xFF1E293B), + ); +} diff --git a/lib/presentation/screens/staff/Enquiry/tabs/policyTab.dart b/lib/presentation/screens/staff/Enquiry/tabs/policyTab.dart index 8215d33..7f5dcfd 100644 --- a/lib/presentation/screens/staff/Enquiry/tabs/policyTab.dart +++ b/lib/presentation/screens/staff/Enquiry/tabs/policyTab.dart @@ -31,6 +31,7 @@ import '../../../../providers/quotation_staff_proivder.dart'; import '../../../../themes/indicators/custom_loader.dart'; import '../../../../themes/indicators/customizd_file_upload.dart'; import '../../../../themes/indicators/date_field_theme.dart'; +import '../../../../themes/indicators/input_field_decoration.dart'; import '../../../../themes/indicators/text_field_theme.dart'; class PolicyStaffTab extends ConsumerStatefulWidget { @@ -52,6 +53,14 @@ class PolicyStaffTabState extends ConsumerState { final GlobalKey>> dropDownKey = GlobalKey>>(); + final GlobalKey>> dropDownKeyBroker = + GlobalKey>>(); + + List> getBrokerData = []; + List> filteredBrokerData = []; + String? selectedBroker; + String? selectedBrokerName; + List tabHeader = [ 'name', 'email', @@ -143,7 +152,7 @@ class PolicyStaffTabState extends ConsumerState { // "premium_amount": controllers["policyPremiumAmount"]?.text, "policy_number": controllers["policyNumber"]?.text, "payment_mode": controllers["policyPaymentMode"]?.text, - + "broker_id": selectedBroker, "tp": controllers["tp"]?.text, "od": controllers["od"]?.text, "pa": controllers["pa"]?.text, @@ -214,7 +223,7 @@ class PolicyStaffTabState extends ConsumerState { controllers["cgst"]?.text = '0'; controllers["sgst"]?.text = '0'; controllers["igst"]?.text = '0'; - + getBroker(); if (enqQuotation != null) { _loadData(enqQuotation); } @@ -321,6 +330,37 @@ class PolicyStaffTabState extends ConsumerState { } } + Future getBroker() async { + print('getBroker called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('Broker'); + + if (response['status'] == 200) { + print('getBroker - ${response['data']}'); + setState(() { + getBrokerData = List>.from(response['data']); + print('API Data - $getBrokerData'); + + filteredBrokerData = List.from(getBrokerData); + print('originalData - $filteredBrokerData'); + }); + } else { + getBrokerData = []; + filteredBrokerData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + void updateEnquiryData(enquiryData) { setState(() { selectedEnquiryId = enquiryData['id']; @@ -394,6 +434,7 @@ class PolicyStaffTabState extends ConsumerState { controllers["premiumTOT"]?.text = policyData['premium_amount']?.toString() ?? ''; + selectedBroker = policyData['broker_id'] ?? ''; // RC FILE String? rcPath = policyData["policy_pdf_file_name"]; if (rcPath != null && rcPath.isNotEmpty) { @@ -490,13 +531,14 @@ class PolicyStaffTabState extends ConsumerState { final bool isUpdating = choosedPolcyId != null; - if (!isUpdating && enqBrokerName == 'Nhance') { + if (!isUpdating && selectedBrokerName == 'Nhance') { + // if (!isUpdating && enqBrokerName == 'Nhance') { ToastHelper.showErrorToast(context, "Policy PDF is required"); return; } // (enqBrokerName != 'Nhance') // File validations - if ((enqBrokerName != 'Nhance') && + if ((selectedBrokerName != 'Nhance') && (docPDFUploadedFile == null || docPDFUploadedFile?.bytes == null) && (docUploadedPDFFileUrlFromApi == null || docUploadedPDFFileUrlFromApi!.isEmpty)) { @@ -1022,54 +1064,62 @@ class PolicyStaffTabState extends ConsumerState { // mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - buildUploadPolicyPdf(context), - SizedBox(width: 20), - Paymentmode(context), - ], - ), - SizedBox(height: 10), - Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - policynumber(context), - SizedBox(width: 20), - policyissuedate(context), - SizedBox(width: 20), - policystartdate(context), - SizedBox(width: 20), - policyenddate(context), + buildBroker(context), + if (selectedBrokerName != null) ...[ + SizedBox(width: 20), + buildUploadPolicyPdf(context), + SizedBox(width: 20), + + Paymentmode(context), + ], ], ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, + if (selectedBrokerName != null) ...[ + SizedBox(height: 10), + Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + policynumber(context), + SizedBox(width: 20), + policyissuedate(context), + SizedBox(width: 20), + policystartdate(context), + SizedBox(width: 20), + policyenddate(context), + ], + ), - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildThirdParty(context), - buildOwnDamage(context), - buildPersonalAccident(context), - ], - ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, - SizedBox(width: 20), - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildCGST(context), - buildSGST(context), - buildIGST(context), - buildPremiumTotal(context), - ], - ), - ], - ), + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildThirdParty(context), + buildOwnDamage(context), + buildPersonalAccident(context), + ], + ), + + SizedBox(width: 20), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildCGST(context), + buildSGST(context), + buildIGST(context), + buildPremiumTotal(context), + ], + ), + ], + ), + ], ], ), ); @@ -1348,6 +1398,154 @@ class PolicyStaffTabState extends ConsumerState { ); } + Widget buildBroker(BuildContext context) { + Map? selectedBrokers = filteredBrokerData.firstWhere( + (item) => item['id'].toString() == selectedBroker, + orElse: () => {}, + ); + return Row( + children: [ + Text('Broker *', style: _textStyle), + SizedBox(width: 15), + SizedBox( + height: 30, + width: MediaQuery.of(context).size.width * 0.1, + child: DropdownSearch>( + key: dropDownKeyBroker, + selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null, + items: (filter, infiniteScrollProps) { + return filteredBrokerData; + }, + + itemAsString: (val) => val['name'].toString(), // what to show + compareFn: (item, selectedItem) => + item['id'] == selectedItem['id'], // ✅ compare by id + validator: (val) { + if (val == null) { + return "Required"; // ✅ error message + } + return null; + }, + suffixProps: DropdownSuffixProps( + // make sure the dropdown button is visible + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, // remove default padding + constraints: const BoxConstraints( + // shrink icon tap area + minWidth: 12, + minHeight: 12, + ), + iconSize: 15, // smaller icon + // icon: const Icon(Icons.arrow_drop_down), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem != null ? selectedItem['name'].toString() : "", + style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Broker", + ).copyWith( + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.black, + width: 0.1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.black, + width: 0.1, + ), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + + popupProps: PopupProps.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + menuProps: MenuProps( + backgroundColor: + Colors.white, // 👈 sets dropdown background to white + ), + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Broker...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + ), + ), + + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + // color: isSelected ? Colors.blue.withOpacity(0.1) : null, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter(fontSize: 12, color: Colors.black), + ), + ); + }, + // constraints: BoxConstraints(), + ), + + onChanged: (val) { + if (val != null) { + print("Selected Broker : ${val['name']}"); + print("Id: ${val['id']}"); + selectedBroker = val['id']; + setState(() { + selectedBrokerName = val['name']; + print('selectedBrokerName - $selectedBrokerName'); + }); + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + Widget Paymentmode(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -1365,7 +1563,7 @@ class PolicyStaffTabState extends ConsumerState { Validators.requiredField(value, "policyPaymentMode"), txtwidth: ResponsiveLayout.isMobile(context) ? null - : MediaQuery.of(context).size.width * 0.15, + : MediaQuery.of(context).size.width * 0.07, ), ], ); @@ -1535,7 +1733,7 @@ class PolicyStaffTabState extends ConsumerState { // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( - width: MediaQuery.of(context).size.width * 0.15, + width: MediaQuery.of(context).size.width * 0.12, child: Text('Upload Policy PDF * ', style: _textStyle), ), // const SizedBox(height: 10), @@ -1577,7 +1775,7 @@ class PolicyStaffTabState extends ConsumerState { docPDFUploadedFile = file; }); - if (enqBrokerName == 'Nhance') { + if (selectedBrokerName == 'Nhance') { handleUploadPolicy(); } }, diff --git a/lib/presentation/screens/staff/Enquiry/tabs/tab.dart b/lib/presentation/screens/staff/Enquiry/tabs/tab.dart index 0674262..9cea939 100644 --- a/lib/presentation/screens/staff/Enquiry/tabs/tab.dart +++ b/lib/presentation/screens/staff/Enquiry/tabs/tab.dart @@ -229,8 +229,8 @@ class TabEnquiryStaffListState extends ConsumerState { children: [ insured_name(context), vehcile_num(context), - vehcile_Type(context), - broker(context), + // vehcile_Type(context), + // broker(context), SizedBox.shrink(), ], ), diff --git a/lib/presentation/screens/staff/enquiry_single_page/model/dropdown_option.dart b/lib/presentation/screens/staff/enquiry_single_page/model/dropdown_option.dart new file mode 100644 index 0000000..ad5bb66 --- /dev/null +++ b/lib/presentation/screens/staff/enquiry_single_page/model/dropdown_option.dart @@ -0,0 +1,13 @@ +class DropdownOption { + final String value; + final String label; + + DropdownOption({required this.value, required this.label}); + + factory DropdownOption.fromJson(Map json) { + return DropdownOption( + value: json['value'] ?? '', + label: json['label'] ?? json['value'] ?? '', + ); + } +} diff --git a/lib/presentation/screens/staff/enquiry_single_page/model/enquiry_model.dart b/lib/presentation/screens/staff/enquiry_single_page/model/enquiry_model.dart new file mode 100644 index 0000000..f11b09a --- /dev/null +++ b/lib/presentation/screens/staff/enquiry_single_page/model/enquiry_model.dart @@ -0,0 +1,99 @@ +class EnquiryModel { + final String? id; + final DateTime receivedDate; + final String partner; + final String assignedTo; + final String broker; + final String insurer; + final String insuredName; + final String vehicleNo; + final String vehicleType; + final String email; + final String mobile; + final String remarks; + final String status; + + EnquiryModel({ + this.id, + required this.receivedDate, + required this.partner, + required this.assignedTo, + required this.broker, + required this.insurer, + required this.insuredName, + required this.vehicleNo, + required this.vehicleType, + required this.email, + required this.mobile, + required this.remarks, + required this.status, + }); + + factory EnquiryModel.fromJson(Map json) { + return EnquiryModel( + id: json['id']?.toString(), + receivedDate: DateTime.parse(json['received_date']), + partner: json['partner'] ?? '', + assignedTo: json['assigned_to'] ?? '', + broker: json['broker'] ?? '', + insurer: json['insurer'] ?? '', + insuredName: json['insured_name'] ?? '', + vehicleNo: json['vehicle_no'] ?? '', + vehicleType: json['vehicle_type'] ?? '', + email: json['email'] ?? '', + mobile: json['mobile'] ?? '', + remarks: json['remarks'] ?? '', + status: json['status'] ?? '', + ); + } + + Map toJson() { + return { + 'id': id, + 'received_date': receivedDate.toIso8601String(), + 'partner': partner, + 'assigned_to': assignedTo, + 'broker': broker, + 'insurer': insurer, + 'insured_name': insuredName, + 'vehicle_no': vehicleNo, + 'vehicle_type': vehicleType, + 'email': email, + 'mobile': mobile, + 'remarks': remarks, + 'status': status, + }; + } + + EnquiryModel copyWith({ + String? id, + DateTime? receivedDate, + String? partner, + String? assignedTo, + String? broker, + String? insurer, + String? insuredName, + String? vehicleNo, + String? vehicleType, + String? email, + String? mobile, + String? remarks, + String? status, + }) { + return EnquiryModel( + id: id ?? this.id, + receivedDate: receivedDate ?? this.receivedDate, + partner: partner ?? this.partner, + assignedTo: assignedTo ?? this.assignedTo, + broker: broker ?? this.broker, + insurer: insurer ?? this.insurer, + insuredName: insuredName ?? this.insuredName, + vehicleNo: vehicleNo ?? this.vehicleNo, + vehicleType: vehicleType ?? this.vehicleType, + email: email ?? this.email, + mobile: mobile ?? this.mobile, + remarks: remarks ?? this.remarks, + status: status ?? this.status, + ); + } +} diff --git a/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_form.dart b/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_form.dart new file mode 100644 index 0000000..d236da7 --- /dev/null +++ b/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_form.dart @@ -0,0 +1,175 @@ +import 'package:flutter/material.dart'; + +import '../model/dropdown_option.dart'; +import '../model/enquiry_model.dart'; +import '../widgets/searchable_dropdown.dart'; + +class EnquiryForm extends StatefulWidget { + final Function(EnquiryModel) onSubmit; + final List partners; + final List insurers; + final List assignees; + + const EnquiryForm({ + super.key, + required this.onSubmit, + required this.partners, + required this.insurers, + required this.assignees, + }); + + @override + State createState() => _EnquiryFormState(); +} + +class _EnquiryFormState extends State { + final _formKey = GlobalKey(); + String? _selectedPartner; + String? _selectedInsurer; + String? _selectedAssignee; + final _insuredNameController = TextEditingController(); + final _regNoController = TextEditingController(); + + @override + void dispose() { + _insuredNameController.dispose(); + _regNoController.dispose(); + super.dispose(); + } + + void _submit() { + if (_formKey.currentState!.validate()) { + final enquiry = EnquiryModel( + receivedDate: DateTime.now(), + partner: _selectedPartner!, + assignedTo: _selectedAssignee!, + broker: 'Nhance', + insurer: _selectedInsurer!, + insuredName: _insuredNameController.text, + vehicleNo: _regNoController.text, + vehicleType: '', + email: '', + mobile: '', + remarks: '', + status: 'awaiting', + ); + + widget.onSubmit(enquiry); + + // Reset form + _formKey.currentState!.reset(); + _insuredNameController.clear(); + _regNoController.clear(); + setState(() { + _selectedPartner = null; + _selectedInsurer = null; + _selectedAssignee = null; + }); + } + } + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(20), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Form( + key: _formKey, + child: Wrap( + spacing: 12, + runSpacing: 12, + alignment: WrapAlignment.start, + children: [ + SizedBox( + width: 180, + child: SearchableDropdown( + label: 'Partner', + value: _selectedPartner, + items: widget.partners, + onChanged: (value) => setState(() => _selectedPartner = value), + validator: (value) => value == null ? 'Required' : null, + ), + ), + SizedBox( + width: 180, + child: SearchableDropdown( + label: 'Insurer', + value: _selectedInsurer, + items: widget.insurers, + onChanged: (value) => setState(() => _selectedInsurer = value), + validator: (value) => value == null ? 'Required' : null, + ), + ), + SizedBox( + width: 180, + child: TextFormField( + controller: _insuredNameController, + decoration: const InputDecoration( + labelText: 'Insured Name', + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, + ), + ), + style: const TextStyle(fontSize: 13), + validator: (value) => + value?.isEmpty ?? true ? 'Required' : null, + ), + ), + SizedBox( + width: 180, + child: TextFormField( + controller: _regNoController, + decoration: const InputDecoration( + labelText: 'Reg No', + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, + ), + ), + style: const TextStyle(fontSize: 13), + validator: (value) => + value?.isEmpty ?? true ? 'Required' : null, + ), + ), + SizedBox( + width: 180, + child: SearchableDropdown( + label: 'Assigned To', + value: _selectedAssignee, + items: widget.assignees, + onChanged: (value) => setState(() => _selectedAssignee = value), + validator: (value) => value == null ? 'Required' : null, + ), + ), + ElevatedButton( + onPressed: _submit, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF4a90e2), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), + ), + child: const Text('Create', style: TextStyle(fontSize: 13)), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_group.dart b/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_group.dart new file mode 100644 index 0000000..49dfa71 --- /dev/null +++ b/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_group.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; + +import '../model/dropdown_option.dart'; +import '../model/enquiry_model.dart'; +import 'enquiry_row.dart'; + +class EnquiryGroup extends StatefulWidget { + final String title; + final String icon; + final String groupKey; + final List enquiries; + final Function(EnquiryModel) onUpdate; + final List partners; + final List insurers; + final List assignees; + + const EnquiryGroup({ + super.key, + required this.title, + required this.icon, + required this.groupKey, + required this.enquiries, + required this.onUpdate, + required this.partners, + required this.insurers, + required this.assignees, + }); + + @override + State createState() => _EnquiryGroupState(); +} + +class _EnquiryGroupState extends State { + bool _isExpanded = true; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + InkWell( + onTap: () => setState(() => _isExpanded = !_isExpanded), + child: Container( + color: const Color(0xFFf8f9fa), + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Icon( + _isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, + size: 24, + ), + const SizedBox(width: 8), + Text( + '${widget.icon} ${widget.title}', + style: const TextStyle(fontWeight: FontWeight.w600), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 2, + ), + decoration: BoxDecoration( + color: const Color(0xFFe3f2fd), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${widget.enquiries.length} Records', + style: const TextStyle( + fontSize: 11, + color: Color(0xFF1976d2), + ), + ), + ), + ], + ), + ), + ), + if (_isExpanded) + ...widget.enquiries.isEmpty + ? [ + const Padding( + padding: EdgeInsets.all(30), + child: Text( + 'No records found', + style: TextStyle( + color: Colors.grey, + fontStyle: FontStyle.italic, + ), + ), + ), + ] + : widget.enquiries.map((enquiry) { + return EnquiryRow( + enquiry: enquiry, + onUpdate: widget.onUpdate, + partners: widget.partners, + insurers: widget.insurers, + assignees: widget.assignees, + ); + }).toList(), + ], + ); + } +} diff --git a/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_row.dart b/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_row.dart new file mode 100644 index 0000000..2627cab --- /dev/null +++ b/lib/presentation/screens/staff/enquiry_single_page/screens/enquiry_row.dart @@ -0,0 +1,255 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../model/dropdown_option.dart'; +import '../model/enquiry_model.dart'; +import '../widgets/searchable_dropdown.dart'; + +class EnquiryRow extends StatefulWidget { + final EnquiryModel enquiry; + final Function(EnquiryModel) onUpdate; + final List partners; + final List insurers; + final List assignees; + + const EnquiryRow({ + super.key, + required this.enquiry, + required this.onUpdate, + required this.partners, + required this.insurers, + required this.assignees, + }); + + @override + State createState() => _EnquiryRowState(); +} + +class _EnquiryRowState extends State { + bool _isEditMode = false; + late EnquiryModel _editedEnquiry; + + @override + void initState() { + super.initState(); + _editedEnquiry = widget.enquiry; + } + + void _toggleEditMode() { + setState(() { + if (_isEditMode) { + widget.onUpdate(_editedEnquiry); + } + _isEditMode = !_isEditMode; + }); + } + + Widget _buildCell(String value, {Function(String)? onChanged}) { + if (_isEditMode && onChanged != null) { + return Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: const Color(0xFFe3f2fd), + border: Border.all(color: const Color(0xFF4a90e2), width: 1), + borderRadius: BorderRadius.circular(3), + ), + child: TextFormField( + initialValue: value, + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.all(4), + ), + onChanged: onChanged, + ), + ); + } + return Text(value, style: const TextStyle(fontSize: 12)); + } + + Widget _buildDropdownCell( + String value, + List options, + Function(String?) onChanged, + ) { + if (_isEditMode) { + return Container( + constraints: const BoxConstraints(minHeight: 36), + child: SearchableDropdown( + label: '', + value: value, + items: options, + onChanged: onChanged, + ), + ); + } + return Text(value, style: const TextStyle(fontSize: 12)); + } + + @override + Widget build(BuildContext context) { + final dateFormat = DateFormat('dd-MM-yyyy\nhh:mm a'); + + return Container( + decoration: BoxDecoration( + color: _isEditMode ? const Color(0xFFf8f9fa) : Colors.white, + border: const Border(bottom: BorderSide(color: Color(0xFFf0f0f0))), + ), + child: IntrinsicHeight( + child: Row( + children: [ + SizedBox( + width: 120, + child: Padding( + padding: const EdgeInsets.all(10), + child: Text( + dateFormat.format(widget.enquiry.receivedDate), + style: const TextStyle(fontSize: 12), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildDropdownCell( + _editedEnquiry.partner, + widget.partners, + (v) => setState( + () => _editedEnquiry = _editedEnquiry.copyWith(partner: v), + ), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildDropdownCell( + _editedEnquiry.assignedTo, + widget.assignees, + (v) => setState( + () => + _editedEnquiry = _editedEnquiry.copyWith(assignedTo: v), + ), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildCell(_editedEnquiry.broker), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildDropdownCell( + _editedEnquiry.insurer, + widget.insurers, + (v) => setState( + () => _editedEnquiry = _editedEnquiry.copyWith(insurer: v), + ), + ), + ), + ), + SizedBox( + width: 150, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildCell( + _editedEnquiry.insuredName, + onChanged: (v) => + _editedEnquiry = _editedEnquiry.copyWith(insuredName: v), + ), + ), + ), + SizedBox( + width: 120, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildCell( + _editedEnquiry.vehicleNo, + onChanged: (v) => + _editedEnquiry = _editedEnquiry.copyWith(vehicleNo: v), + ), + ), + ), + SizedBox( + width: 120, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildCell( + _editedEnquiry.vehicleType, + onChanged: (v) => + _editedEnquiry = _editedEnquiry.copyWith(vehicleType: v), + ), + ), + ), + SizedBox( + width: 150, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildCell( + _editedEnquiry.email, + onChanged: (v) => + _editedEnquiry = _editedEnquiry.copyWith(email: v), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildCell( + _editedEnquiry.mobile, + onChanged: (v) => + _editedEnquiry = _editedEnquiry.copyWith(mobile: v), + ), + ), + ), + SizedBox( + width: 150, + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildCell( + _editedEnquiry.remarks, + onChanged: (v) => + _editedEnquiry = _editedEnquiry.copyWith(remarks: v), + ), + ), + ), + SizedBox( + width: 80, + child: Padding( + padding: const EdgeInsets.all(10), + child: Row( + children: [ + IconButton( + icon: Text(_isEditMode ? '💾' : '✏️'), + onPressed: _toggleEditMode, + tooltip: _isEditMode ? 'Save' : 'Edit', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + const SizedBox(width: 8), + IconButton( + icon: const Text('📄'), + onPressed: () {}, + tooltip: 'Documents', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/screens/staff/enquiry_single_page/widgets/enquiry_screen.dart b/lib/presentation/screens/staff/enquiry_single_page/widgets/enquiry_screen.dart new file mode 100644 index 0000000..ef4aaff --- /dev/null +++ b/lib/presentation/screens/staff/enquiry_single_page/widgets/enquiry_screen.dart @@ -0,0 +1,413 @@ +import 'package:flutter/material.dart'; + +import '../../../../../core/services/api_service.dart'; +import '../../../../layouts/appheader.dart'; +import '../model/dropdown_option.dart'; +import '../model/enquiry_model.dart'; +import '../screens/enquiry_form.dart'; +import '../screens/enquiry_group.dart'; + +class EnquiryScreen extends StatefulWidget { + const EnquiryScreen({super.key}); + + @override + State createState() => _EnquiryScreenState(); +} + +class _EnquiryScreenState extends State { + List _enquiries = []; + List _partners = []; + List _insurers = []; + List _assignees = []; + bool _isLoading = true; + String _searchQuery = ''; + + @override + void initState() { + super.initState(); + _loadData(); + } + + Future _loadData() async { + setState(() => _isLoading = true); + try { + final results = await Future.wait([ + ApiService.fetchEnquiries(), + ApiService.fetchDropdownOptions('partners'), + ApiService.fetchDropdownOptions('insurers'), + ApiService.fetchDropdownOptions('assignees'), + ]); + + setState(() { + _enquiries = results[0] as List; + _partners = results[1] as List; + _insurers = results[2] as List; + _assignees = results[3] as List; + _isLoading = false; + }); + } catch (e) { + setState(() => _isLoading = false); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error loading data: $e'))); + } + } + + Future _createEnquiry(EnquiryModel enquiry) async { + try { + final created = await ApiService.createEnquiry(enquiry); + setState(() { + _enquiries.insert(0, created); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Enquiry created successfully')), + ); + } catch (e) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error creating enquiry: $e'))); + } + } + + Future _updateEnquiry(EnquiryModel enquiry) async { + try { + final updated = await ApiService.updateEnquiry(enquiry.id!, enquiry); + setState(() { + final index = _enquiries.indexWhere((e) => e.id == enquiry.id); + if (index != -1) { + _enquiries[index] = updated; + } + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Enquiry updated successfully')), + ); + } catch (e) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error updating enquiry: $e'))); + } + } + + Map> _groupEnquiries() { + final filtered = _enquiries.where((e) { + if (_searchQuery.isEmpty) return true; + final query = _searchQuery.toLowerCase(); + return e.partner.toLowerCase().contains(query) || + e.insuredName.toLowerCase().contains(query) || + e.vehicleNo.toLowerCase().contains(query) || + e.email.toLowerCase().contains(query) || + e.mobile.contains(query); + }).toList(); + + return { + 'awaiting': filtered.where((e) => e.status == 'awaiting').toList(), + 'created': filtered.where((e) => e.status == 'created').toList(), + 'accepted': filtered.where((e) => e.status == 'accepted').toList(), + 'rejected': filtered.where((e) => e.status == 'rejected').toList(), + 'policy': filtered.where((e) => e.status == 'policy').toList(), + }; + } + + @override + Widget build(BuildContext context) { + final groupedEnquiries = _groupEnquiries(); + + return Scaffold( + appBar: const AppHeader(), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + EnquiryForm( + onSubmit: _createEnquiry, + partners: _partners, + insurers: _insurers, + assignees: _assignees, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + '📋 Enquiries', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w600, + ), + ), + SizedBox( + width: 250, + child: TextField( + decoration: const InputDecoration( + hintText: 'Search...', + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + ), + onChanged: (value) { + setState(() => _searchQuery = value); + }, + ), + ), + ], + ), + ), + const SizedBox(height: 15), + Expanded( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: SingleChildScrollView( + child: Column( + children: [ + // Table Header + Container( + color: const Color(0xFFf8f9fa), + child: const Row( + children: [ + SizedBox( + width: 120, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Received Date', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Partner', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Assigned To', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Broker', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Insurer', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 150, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Insured Name', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 120, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Vehicle No', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 120, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Vehicle Type', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 150, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Email', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 100, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Mobile', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 150, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Remarks', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + SizedBox( + width: 80, + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Actions', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF555555), + ), + ), + ), + ), + ], + ), + ), + // Grouped Enquiries + EnquiryGroup( + title: 'Awaiting Proposal', + icon: '🕐', + groupKey: 'awaiting', + enquiries: groupedEnquiries['awaiting']!, + onUpdate: _updateEnquiry, + partners: _partners, + insurers: _insurers, + assignees: _assignees, + ), + EnquiryGroup( + title: 'Proposal Created', + icon: '📝', + groupKey: 'created', + enquiries: groupedEnquiries['created']!, + onUpdate: _updateEnquiry, + partners: _partners, + insurers: _insurers, + assignees: _assignees, + ), + EnquiryGroup( + title: 'Proposal Accepted', + icon: '✅', + groupKey: 'accepted', + enquiries: groupedEnquiries['accepted']!, + onUpdate: _updateEnquiry, + partners: _partners, + insurers: _insurers, + assignees: _assignees, + ), + EnquiryGroup( + title: 'Proposal Rejected', + icon: '❌', + groupKey: 'rejected', + enquiries: groupedEnquiries['rejected']!, + onUpdate: _updateEnquiry, + partners: _partners, + insurers: _insurers, + assignees: _assignees, + ), + EnquiryGroup( + title: 'Policy Created', + icon: '📋', + groupKey: 'policy', + enquiries: groupedEnquiries['policy']!, + onUpdate: _updateEnquiry, + partners: _partners, + insurers: _insurers, + assignees: _assignees, + ), + ], + ), + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/staff/enquiry_single_page/widgets/s b/lib/presentation/screens/staff/enquiry_single_page/widgets/s new file mode 100644 index 0000000..a935b0b --- /dev/null +++ b/lib/presentation/screens/staff/enquiry_single_page/widgets/s @@ -0,0 +1,473 @@ +// 5. Replace your existing _buildDataTable method with this grouped version +Widget _buildDataTable(BuildContext context) { + if (filteredData.isEmpty) { + return const SizedBox( + height: 50, + child: Center(child: Text('No available data')), + ); + } + + final isDesktop = !ResponsiveLayout.isMobile(context); + final groupedData = _groupedEnquiries; + + return LayoutBuilder( + builder: (context, constraints) { + double minWidth = constraints.maxWidth < 1300 ? 1300 : constraints.maxWidth; + + return ScrollConfiguration( + behavior: const MaterialScrollBehavior().copyWith( + dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch}, + ), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth), + child: Column( + children: [ + // Table Header (sticky) + Container( + color: Color(0xFFEDF6F5), + child: _buildTableHeader(isDesktop), + ), + + // Grouped Rows + ...groupedData.entries.map((entry) { + final status = entry.key; + final items = entry.value; + final isExpanded = groupExpanded[status] ?? true; + + return Column( + children: [ + // Group Header + _buildGroupHeader(status, items.length), + + // Group Rows (if expanded) + if (isExpanded) + ...items.isEmpty + ? [ + Padding( + padding: EdgeInsets.all(20), + child: Text( + 'No records found', + style: GoogleFonts.inter( + fontSize: 12, + color: Colors.grey, + fontStyle: FontStyle.italic, + ), + ), + ), + ] + : items.asMap().entries.map((itemEntry) { + final index = itemEntry.key; + final item = itemEntry.value; + final startIndex = (currentPage - 1) * itemsPerPage; + final sno = startIndex + index + 1; + + return _buildGroupedDataRow( + item, + sno, + isDesktop, + ); + }).toList(), + ], + ); + }).toList(), + ], + ), + ), + ), + ), + ); + }, + ); +} + +// 6. Build table header +Widget _buildTableHeader(bool isDesktop) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + _buildHeaderCell('Received Date', 120), + _buildHeaderCell('Partner *', 110), + _buildHeaderCell('Assigned To *', 110), + _buildHeaderCell('Broker *', 100), + _buildHeaderCell('Insurer *', 110), + _buildHeaderCell('Insured Name *', 120), + _buildHeaderCell('Vehicle No *', 120), + _buildHeaderCell('Vehicle Type *', 110), + _buildHeaderCell('Email', 150), + _buildHeaderCell('Mobile', 100), + _buildHeaderCell('Documents', 100), + _buildHeaderCell('Remarks', 120), + if (roleId != 'staff') _buildHeaderCell('Action', 100), + _buildHeaderCell('Assigned Date', 120), + _buildHeaderCell('Premium', 100), + _buildHeaderCell('Payment Mode', 100), + _buildHeaderCell('Policy Number', 120), + _buildHeaderCell('Status', 100), + ], + ), + ); +} + +Widget _buildHeaderCell(String label, double width) { + return Container( + width: width, + padding: EdgeInsets.symmetric(horizontal: 4), + child: Text( + label, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ); +} + +// 7. Build grouped data row (container-based instead of DataTable) +Widget _buildGroupedDataRow( + Map item, + int sno, + bool isDesktop, +) { + final id = int.tryParse(item['id'].toString()) ?? 0; + final selectId = item['id'].toString() ?? '0'; + final editing = isEditingRow[id] ?? false; + + final bool isNewRow = + int.tryParse(id.toString()) != null && + int.parse(id.toString()) > 1000000000000; + + return Container( + decoration: BoxDecoration( + color: editing ? Color(0xFFf8f9fa) : Colors.white, + border: Border( + bottom: BorderSide(color: Colors.grey.shade200, width: 0.5), + ), + ), + child: InkWell( + onTap: () { + // Optional: handle row tap + }, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + // Received Date + _buildDataCell( + formatDateTimeForTable(item['created_on'], newLine: true), + 120, + ), + + // Partner + _buildDataCell( + editing ? null : item['agent_name'] ?? '-', + 110, + widget: editing ? buildAgentName(context, fromHeader: false) : null, + ), + + // Assigned To + _buildDataCell( + editing ? null : item['assigned_to_name'] ?? '-', + 110, + widget: editing ? buildSelectStaffMem(context) : null, + subtitle: editing ? null : item['agent_code'], + ), + + // Broker + _buildDataCell( + editing ? null : item['broker_name'] ?? '-', + 100, + widget: editing ? buildBroker(context) : null, + ), + + // Insurer + _buildDataCell( + editing ? null : item['insurer_short_name'] ?? '-', + 110, + widget: editing ? buildInsurer(context, fromHeader: false) : null, + ), + + // Insured Name + _buildDataCell( + editing ? null : item['insured_name'] ?? '-', + 120, + widget: editing ? buildInsuredName(context, fromHeader: false) : null, + ), + + // Vehicle No + _buildDataCell( + editing ? null : item['reg_no'] ?? '-', + 120, + widget: editing ? buildVehicleNumber(context) : null, + ), + + // Vehicle Type + _buildDataCell( + editing ? null : _wrapText(item['vehicle_type'] ?? '-', 13), + 110, + widget: editing ? buildVehicleType(context) : null, + ), + + // Email + _buildDataCell( + editing ? null : item['email'] ?? '-', + 150, + widget: editing ? buildEmail(context) : null, + ), + + // Mobile + _buildDataCell( + editing ? null : item['mobile'] ?? '-', + 100, + widget: editing ? buildPhNumber(context) : null, + ), + + // Documents + _buildDocumentCell(selectId, editing, 100), + + // Remarks + _buildDataCell( + editing ? null : _truncateText(item['remarks'] ?? '-', 10), + 120, + widget: editing ? buildRemarks(context) : null, + tooltip: item['remarks'], + ), + + // Action (if not staff) + if (roleId != 'staff') + _buildActionCell(item, id, editing, isNewRow, 100), + + // Assigned Date + _buildDataCell( + formatDateTimeForTable(item['updated_on'], newLine: true), + 120, + ), + + // Premium + _buildPremiumCell(item, 100), + + // Payment Mode + _buildDataCell(item['payment_mode'] ?? '-', 100), + + // Policy Number + _buildDataCell(item['policy_number'] ?? '-', 120), + + // Status + _buildStatusCell(item, 100), + ], + ), + ), + ), + ); +} + +// 8. Helper widget builders +Widget _buildDataCell( + String? text, + double width, { + Widget? widget, + String? subtitle, + String? tooltip, +}) { + if (widget != null) { + return Container( + width: width, + padding: EdgeInsets.symmetric(horizontal: 4), + child: widget, + ); + } + + Widget child = Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + text ?? '-', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + if (subtitle != null) + Text( + subtitle, + style: GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + ), + ], + ); + + if (tooltip != null && tooltip.isNotEmpty) { + child = Tooltip( + message: tooltip, + waitDuration: Duration(milliseconds: 400), + child: child, + ); + } + + return Container( + width: width, + padding: EdgeInsets.symmetric(horizontal: 4), + child: child, + ); +} + +Widget _buildDocumentCell(String selectId, bool editing, double width) { + return Container( + width: width, + padding: EdgeInsets.symmetric(horizontal: 4), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () async { + final RenderBox button = context.findRenderObject() as RenderBox; + final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final Offset position = button.localToGlobal(Offset.zero, ancestor: overlay); + + await showMenu( + context: context, + position: RelativeRect.fromLTRB( + position.dx, + position.dy + button.size.height, + overlay.size.width, + 0, + ), + items: [ + PopupMenuItem( + child: Column( + mainAxisSize: MainAxisSize.min, + children: _buildPopupDownload(context, selectId, editing), + ), + ), + ], + color: Colors.white, + ); + }, + child: Tooltip( + message: editing ? 'Click To Upload Documents' : 'Click To View Documents', + child: Icon( + editing ? Icons.upload_file : Icons.download_for_offline, + color: editing ? Colors.blue : Colors.green, + size: 20, + ), + ), + ), + ), + ); +} + +Widget _buildActionCell( + Map item, + int id, + bool editing, + bool isNewRow, + double width, +) { + return Container( + width: width, + padding: EdgeInsets.symmetric(horizontal: 4), + child: Row( + children: [ + if (editing) ...[ + IconButton( + icon: Icon(Icons.check, color: Colors.green, size: 18), + tooltip: "Save", + onPressed: () => handleSave(id), + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + ), + SizedBox(width: 4), + IconButton( + icon: Icon( + isNewRow ? Icons.close : Icons.edit_off_sharp, + color: isNewRow ? Colors.red : Colors.blue, + size: 18, + ), + tooltip: isNewRow ? "Delete" : "Cancel Edit Mode", + onPressed: () => handleCancel(id), + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + ), + ] else ...[ + IconButton( + icon: Icon(Icons.edit, color: Colors.blue, size: 18), + tooltip: "Edit", + onPressed: () => handleEdit(item), + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + ), + ], + ], + ), + ); +} + +Widget _buildPremiumCell(Map item, double width) { + final showApproval = item['status'] == 'Proposal Created' || + item['status'] == 'Proposal Rejected'; + + return Container( + width: width, + padding: EdgeInsets.symmetric(horizontal: 4), + child: showApproval + ? InkWell( + onTap: () => handleProposalAccept(context, item['id']), + child: Text( + 'Click To\nApprove', + style: GoogleFonts.inter( + color: Colors.green, + fontSize: 11, + fontWeight: FontWeight.w700, + ), + ), + ) + : Text( + item['premium_amount']?.toString() ?? '-', + style: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.w500), + ), + ); +} + +Widget _buildStatusCell(Map item, double width) { + return Container( + width: width, + padding: EdgeInsets.symmetric(horizontal: 4), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () async { + var status = item['status']; + var enqId = item['id']; + + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('enqStaffDataId'); + await prefs.setString('enqStaffDataId', enqId.toString()); + ref.read(quotationStaffIdProvider.notifier).state = enqId; + buildStatusActions(context, status, enqId); + }, + child: Tooltip( + message: 'Click to View or Process Enquiry', + waitDuration: Duration(milliseconds: 500), + child: Text( + item['status'] ?? '-', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + ); +} \ No newline at end of file diff --git a/lib/presentation/screens/staff/enquiry_single_page/widgets/searchable_dropdown.dart b/lib/presentation/screens/staff/enquiry_single_page/widgets/searchable_dropdown.dart new file mode 100644 index 0000000..93c607f --- /dev/null +++ b/lib/presentation/screens/staff/enquiry_single_page/widgets/searchable_dropdown.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:dropdown_search/dropdown_search.dart'; + +import '../model/dropdown_option.dart'; + +class SearchableDropdown extends StatelessWidget { + final String label; + final String? value; + final List items; + final Function(String?) onChanged; + final String? Function(String?)? validator; + final bool enabled; + + const SearchableDropdown({ + super.key, + required this.label, + required this.value, + required this.items, + required this.onChanged, + this.validator, + this.enabled = true, + }); + + @override + Widget build(BuildContext context) { + return DropdownSearch( + // items: items, + selectedItem: items.where((item) => item.value == value).firstOrNull, + itemAsString: (item) => item.label, + onChanged: (item) => onChanged(item?.value), + enabled: enabled, + // dropdownDecoratorProps: DropDownDecoratorProps( + // dropdownSearchDecoration: InputDecoration( + // labelText: label, + // border: const OutlineInputBorder(), + // contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + // ), + // ), + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: const TextFieldProps( + decoration: InputDecoration( + hintText: 'Search...', + prefixIcon: Icon(Icons.search), + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), + ), + ), + // itemBuilder: (context, item, isSelected) { + // return Container( + // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + // decoration: BoxDecoration( + // color: isSelected ? const Color(0xFFe3f2fd) : null, + // ), + // child: Text( + // item.label, + // style: TextStyle( + // fontSize: 13, + // fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + // ), + // ), + // ); + // }, + menuProps: const MenuProps( + backgroundColor: Colors.white, + elevation: 8, + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + searchDelay: const Duration(milliseconds: 300), + emptyBuilder: (context, searchEntry) { + return const Center( + child: Padding( + padding: EdgeInsets.all(20), + child: Text( + 'No items found', + style: TextStyle(color: Colors.grey, fontSize: 13), + ), + ), + ); + }, + ), + compareFn: (item1, item2) => item1.value == item2.value, + // validator: validator, + dropdownBuilder: (context, selectedItem) { + return Text( + selectedItem?.label ?? '', + style: const TextStyle(fontSize: 13), + ); + }, + ); + } +} diff --git a/lib/presentation/themes/indicators/customizd_file_upload.dart b/lib/presentation/themes/indicators/customizd_file_upload.dart index c5148d9..478cc8f 100644 --- a/lib/presentation/themes/indicators/customizd_file_upload.dart +++ b/lib/presentation/themes/indicators/customizd_file_upload.dart @@ -132,6 +132,10 @@ class _ThemedUploadFieldState extends State { ), ), ), + // const Icon( + // Icons.file_upload_outlined, + // color: Colors.black, + // ), ], ], ), diff --git a/lib/presentation/themes/indicators/date_field_theme.dart b/lib/presentation/themes/indicators/date_field_theme.dart index f92bc68..6687ccf 100644 --- a/lib/presentation/themes/indicators/date_field_theme.dart +++ b/lib/presentation/themes/indicators/date_field_theme.dart @@ -85,7 +85,7 @@ class _ThemedDateFieldState extends State { width: widget.txtwidth ?? MediaQuery.of(context).size.width, height: widget.txtheight, padding: const EdgeInsets.symmetric( - vertical: 10, + vertical: 8, horizontal: 15, ), decoration: BoxDecoration( @@ -94,7 +94,7 @@ class _ThemedDateFieldState extends State { border: Border.all( color: hasError ? (widget.errorBorderColor ?? Color(0xFFD83731)) - : (widget.borderColor ?? Colors.grey.shade50), + : (widget.borderColor ?? Color(0xFFE2E8F0)), width: 1, ), ), @@ -107,7 +107,7 @@ class _ThemedDateFieldState extends State { ? widget.controller!.text : widget.hintText ?? "Select Date", style: TextStyle( - fontSize: 13, + fontSize: 11, color: hasError ? Color(0xFFD83731) : Colors.black, ), overflow: TextOverflow.ellipsis, @@ -116,7 +116,8 @@ class _ThemedDateFieldState extends State { const Icon( Icons.calendar_today, size: 16, - color: Colors.black, + + color: Color(0xFF5A6C7D), ), ], ), diff --git a/lib/presentation/themes/indicators/export_btn.dart b/lib/presentation/themes/indicators/export_btn.dart index 25e5c54..e8665c2 100644 --- a/lib/presentation/themes/indicators/export_btn.dart +++ b/lib/presentation/themes/indicators/export_btn.dart @@ -74,7 +74,8 @@ class ExportBtn extends HookWidget { child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: const Color(0xFF425B5B), + color: const Color(0xFF2E7D6E), + // color: const Color(0xFF425B5B), borderRadius: BorderRadius.circular(8.0), ), child: Row( diff --git a/lib/presentation/themes/indicators/input_field_decoration.dart b/lib/presentation/themes/indicators/input_field_decoration.dart index c920976..f876257 100644 --- a/lib/presentation/themes/indicators/input_field_decoration.dart +++ b/lib/presentation/themes/indicators/input_field_decoration.dart @@ -4,7 +4,7 @@ class AppInputDecorations { static InputDecoration dropdownDecoration({String? label, String? hint}) { return InputDecoration( labelText: label, - labelStyle: TextStyle(fontSize: 12), + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), hintText: hint, filled: true, fillColor: const Color(0xFFECECEC), diff --git a/lib/presentation/themes/indicators/search_field_theme.dart b/lib/presentation/themes/indicators/search_field_theme.dart index 829a1a9..0fd7142 100644 --- a/lib/presentation/themes/indicators/search_field_theme.dart +++ b/lib/presentation/themes/indicators/search_field_theme.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:google_fonts/google_fonts.dart'; // import 'package:uae_stat/config/my_theme.dart'; @@ -55,20 +56,23 @@ class ThemedSearchField extends HookWidget { // contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 15), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Color(0xFFF6F8F8)), + borderSide: BorderSide(color: Color(0xFFE2E8F0)), + // borderSide: BorderSide(color: Color(0xFFF6F8F8)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Color(0xFFF6F8F8)), + // borderSide: BorderSide(color: Color(0xFFF6F8F8)), + borderSide: BorderSide(color: Color(0xFFE2E8F0)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Color(0xFF50A398), width: 2), ), hintText: hintText, - hintStyle: TextStyle( - color: const Color(0xFF686868), - fontSize: (txtHeight ?? 45) <= 45 ? 14 : 16, + hintStyle: GoogleFonts.inter( + // color: const Color(0xFF686868), + color: const Color(0xFF94A3B8), + fontSize: (txtHeight ?? 45) <= 45 ? 12 : 14, ), // suffixIconConstraints: const BoxConstraints( // maxWidth: 25 + 16 + 10, @@ -76,7 +80,8 @@ class ThemedSearchField extends HookWidget { // ), suffixIcon: Padding( padding: const EdgeInsetsDirectional.only(start: 16, end: 10), - child: Icon(Icons.search, color: const Color(0xFF686868), size: 20), + child: Icon(Icons.search, color: const Color(0xFF94A3B8), size: 18), + // child: Icon(Icons.search, color: const Color(0xFF686868), size: 20), ), ); diff --git a/lib/presentation/themes/indicators/text_field_theme.dart b/lib/presentation/themes/indicators/text_field_theme.dart index cb2ab17..98bf402 100644 --- a/lib/presentation/themes/indicators/text_field_theme.dart +++ b/lib/presentation/themes/indicators/text_field_theme.dart @@ -31,12 +31,14 @@ class ThemedFormField extends HookWidget { this.verticalPad, this.horizonalPad, this.enableBorderWidth, + this.textColr, }); final double? verticalPad; final double? horizonalPad; final double? enableBorderWidth; final String? hintText; + final Color? textColr; final String? imgPath; final String? Function(String? text)? validator; @@ -130,7 +132,10 @@ class ThemedFormField extends HookWidget { ) : OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Color(0xFF50A398), width: 2), + borderSide: BorderSide( + color: borderColor ?? Color(0xFF50A398), + width: 2, + ), ), hintText: hintText, hintStyle: TextStyle( @@ -187,7 +192,7 @@ class ThemedFormField extends HookWidget { style: GoogleFonts.inter( fontSize: 12, // 👈 Change this to your desired size fontWeight: FontWeight.w400, // optional - color: Colors.black, // optional + color: textColr ?? Colors.black, // optional ), ), ), diff --git a/lib/presentation/themes/indicators/text_field_theme_inline_editor.dart b/lib/presentation/themes/indicators/text_field_theme_inline_editor.dart index 1f81a87..9bd4c76 100644 --- a/lib/presentation/themes/indicators/text_field_theme_inline_editor.dart +++ b/lib/presentation/themes/indicators/text_field_theme_inline_editor.dart @@ -18,6 +18,7 @@ class ThemedFormInlineField extends HookWidget { this.isObscurable = false, this.hintColor, this.backgroundColor, + this.textColr, this.borderColor, this.errorBorderColor, this.errorTextColor, @@ -41,6 +42,7 @@ class ThemedFormInlineField extends HookWidget { final Color? borderColor; final Color? errorBorderColor; final Color? errorTextColor; + final Color? textColr; final double? txtwidth; final bool readOnly; final bool? isdense; @@ -96,14 +98,14 @@ class ThemedFormInlineField extends HookWidget { filled: true, fillColor: backgroundColor ?? Colors.white, // contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 2), - contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), // borderSide: BorderSide(color: Color(0xFF50A398)), borderSide: BorderSide( color: borderColor ?? Color(0xFFFFFFFF), - width: 0.1, + width: 0.5, ), ), enabledBorder: OutlineInputBorder( @@ -111,7 +113,7 @@ class ThemedFormInlineField extends HookWidget { // borderSide: BorderSide(color: Color(0xFF50A398)), borderSide: BorderSide( color: borderColor ?? backgroundColor ?? Color(0xFFFFFFFF), - width: 0.1, + width: 0.5, ), ), focusedBorder: readOnly @@ -122,7 +124,10 @@ class ThemedFormInlineField extends HookWidget { ) : OutlineInputBorder( borderRadius: BorderRadius.circular(5), - borderSide: BorderSide(color: Color(0xFF50A398), width: 2), + borderSide: BorderSide( + color: borderColor ?? Color(0xFF50A398), + width: 2, + ), ), hintText: hintText, hintStyle: TextStyle( @@ -177,7 +182,7 @@ class ThemedFormInlineField extends HookWidget { style: GoogleFonts.inter( fontSize: 12, // 👈 Change this to your desired size fontWeight: FontWeight.w400, // optional - color: Colors.black, // optional + color: textColr ?? Colors.black, // optional ), ), ), diff --git a/lib/presentation/widgets/custom_Stdate_EnDate_Filter.dart b/lib/presentation/widgets/custom_Stdate_EnDate_Filter.dart index 3a3af0f..837ffcc 100644 --- a/lib/presentation/widgets/custom_Stdate_EnDate_Filter.dart +++ b/lib/presentation/widgets/custom_Stdate_EnDate_Filter.dart @@ -196,7 +196,12 @@ class _DateFilterRowState extends ConsumerState { child: Tooltip( message: 'Filter', child: IconButton( - icon: const Icon(Icons.filter_alt_outlined), + icon: const Icon( + Icons.filter_alt_outlined, + size: 18, + color: const Color(0xFF94A3B8), + ), + onPressed: () { if (widget.formKey.currentState!.validate()) { widget.onFilter(); @@ -212,7 +217,11 @@ class _DateFilterRowState extends ConsumerState { message: 'Refresh', child: IconButton( onPressed: widget.onRefresh, - icon: const Icon(Icons.refresh), + icon: const Icon( + Icons.refresh, + size: 18, + color: const Color(0xFF94A3B8), + ), ), ), ), @@ -251,20 +260,25 @@ class _DateFilterRowState extends ConsumerState { children: [ // Text('Start Date', style: _textStyle), // SizedBox(height: 5), - ThemedDateField( - hintText: "Select Date", - txtwidth: ResponsiveLayout.isMobile(context) - ? null - : MediaQuery.of(context).size.width * 0.1, - // txtheight: 50, - // backgroundColor: const Color(0xFFECECEC), - // validator: (value) => Validators.requiredField(value, "date"), - controller: widget.startController, - onDateSelected: (date) { - print("Picked Date: $date"); - widget.startController.text = DateFormat('dd-MM-yyyy').format(date); - // controllers['date']?.text = date as String; - }, + SizedBox( + height: 35, + child: ThemedDateField( + hintText: "Select Date", + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.1, + // txtheight: 50, + // backgroundColor: const Color(0xFFECECEC), + // validator: (value) => Validators.requiredField(value, "date"), + controller: widget.startController, + onDateSelected: (date) { + print("Picked Date: $date"); + widget.startController.text = DateFormat( + 'dd-MM-yyyy', + ).format(date); + // controllers['date']?.text = date as String; + }, + ), ), ], ); @@ -276,35 +290,38 @@ class _DateFilterRowState extends ConsumerState { children: [ // Text('End Date', style: _textStyle), // SizedBox(height: 5), - ThemedDateField( - hintText: "Select Date", - txtwidth: ResponsiveLayout.isMobile(context) - ? null - : MediaQuery.of(context).size.width * 0.1, - // txtheight: 50, - // backgroundColor: const Color(0xFFECECEC), - validator: (value) { - final fromText = widget.startController.text ?? ''; - if (fromText.isNotEmpty) { - final startDate = DateFormat('dd-MM-yyyy').parse(fromText); - if (value == null || value.isEmpty) { - return "End Date is required"; - } - final endDate = DateFormat('dd-MM-yyyy').parse(value); + SizedBox( + height: 35, + child: ThemedDateField( + hintText: "Select Date", + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.1, + // txtheight: 50, + // backgroundColor: const Color(0xFFECECEC), + validator: (value) { + final fromText = widget.startController.text ?? ''; + if (fromText.isNotEmpty) { + final startDate = DateFormat('dd-MM-yyyy').parse(fromText); + if (value == null || value.isEmpty) { + return "End Date is required"; + } + final endDate = DateFormat('dd-MM-yyyy').parse(value); - if (endDate.isBefore(startDate)) { - return "End Date cannot be earlier than Start Date"; + if (endDate.isBefore(startDate)) { + return "End Date cannot be earlier than Start Date"; + } } - } - return null; // ✅ no error - }, - controller: widget.endController, - lastDate: DateTime.now(), - onDateSelected: (date) { - print("Picked Date: $date"); - widget.endController.text = DateFormat('dd-MM-yyyy').format(date); - // controllers['date']?.text = date as String; - }, + return null; // ✅ no error + }, + controller: widget.endController, + lastDate: DateTime.now(), + onDateSelected: (date) { + print("Picked Date: $date"); + widget.endController.text = DateFormat('dd-MM-yyyy').format(date); + // controllers['date']?.text = date as String; + }, + ), ), ], ); @@ -339,11 +356,11 @@ class _DateFilterRowState extends ConsumerState { // Text('Status', style: _textStyle), // SizedBox(height: 5), SizedBox( - height: 40, + height: 35, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(5.0), - border: Border.all(color: Colors.grey.shade100), + border: Border.all(color: Color(0xFFE2E8F0)), // color: Colors.black, ), width: ResponsiveLayout.isMobile(context) @@ -390,7 +407,7 @@ class _DateFilterRowState extends ConsumerState { isDense: true, // 👈 reduces built-in vertical padding contentPadding: const EdgeInsets.symmetric( - horizontal: 2, + horizontal: 6, vertical: 1, // 👈 adjust this to make the field shorter ), ), @@ -480,11 +497,11 @@ class _DateFilterRowState extends ConsumerState { // Text("Staff Name", style: _textStyle), // SizedBox(height: 10), SizedBox( - height: 40, + height: 35, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(5.0), - border: Border.all(color: Colors.grey.shade100), + border: Border.all(color: Color(0xFFE2E8F0)), // color: Colors.black, ), @@ -524,7 +541,7 @@ class _DateFilterRowState extends ConsumerState { fillColor: Colors.white, // 👈 makes the dropdown input white contentPadding: const EdgeInsets.symmetric( - horizontal: 2, + horizontal: 6, vertical: 1, // 👈 adjust this to make the field shorter ), ), diff --git a/lib/presentation/widgets/drawer_menu.dart b/lib/presentation/widgets/drawer_menu.dart index 4890f3e..a1e6419 100644 --- a/lib/presentation/widgets/drawer_menu.dart +++ b/lib/presentation/widgets/drawer_menu.dart @@ -137,9 +137,11 @@ class DrawerMenuState extends ConsumerState { _hidePopup(); if (roleId == 'agent') { context.go(AppRoutes.enquiryLst); - } else if (roleId == 'handler') { - context.go(AppRoutes.enquiryHandlerLst); - } else { + } + // else if (roleId == 'handler') { + // context.go(AppRoutes.enquiryHandlerLst); + // } + else { context.go(AppRoutes.enquiryForStaff); } },