diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 560ec9f..f6f2bb8 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.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","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.2.1/","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.7/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.31/","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.2.1/","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.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.13/","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.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage_macos","path":"/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.2.1/","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.7/","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.2/","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.7/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","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.5/","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.7/","dependencies":[],"dev_dependency":false},{"name":"firebase_auth_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth_web-6.1.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core_web-3.3.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_messaging_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging_web-4.1.0/","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.1.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_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-12-08 18:19:11.351183","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.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","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.2.1/","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.7/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/home/venba/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.31/","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.2.1/","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.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/home/venba/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.13/","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.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage_macos","path":"/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.2.1/","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.7/","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.2/","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.7/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","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.5/","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.7/","dependencies":[],"dev_dependency":false},{"name":"firebase_auth_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_auth_web-6.1.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_core_web-3.3.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_messaging_web","path":"/home/venba/.pub-cache/hosted/pub.dev/firebase_messaging_web-4.1.0/","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.1.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_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-12-10 09:42:53.157453","version":"3.32.1","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/lib/core/routing/app_router.dart b/lib/core/routing/app_router.dart index 411398a..cc1f026 100644 --- a/lib/core/routing/app_router.dart +++ b/lib/core/routing/app_router.dart @@ -19,6 +19,8 @@ import '../../presentation/screens/StaffAttendance/individual_Attendance.dart'; import '../../presentation/screens/UserManagement/Agent/agent.dart'; import '../../presentation/screens/UserManagement/Agent/agentList.dart'; import '../../presentation/screens/UserManagement/Profile/profile_mobile.dart'; +import '../../presentation/screens/UserManagement/SalesExecutive/salesExecutive.dart'; +import '../../presentation/screens/UserManagement/SalesExecutive/salesExecutiveList.dart'; import '../../presentation/screens/UserManagement/Staff/staff.dart'; import '../../presentation/screens/UserManagement/Staff/staffList.dart'; import '../../presentation/screens/dashboard/dashboard.dart'; @@ -114,6 +116,24 @@ final GoRouter appRouter = GoRouter( } }, ), + GoRoute( + path: AppRoutes.salesExecutiveLst, + // builder: (context, state) => const Agent(), + builder: (context, state) => const SalesExecutiveList(), + ), + + GoRoute( + path: '/salesExecutive/:id', + builder: (context, state) { + final id = state.pathParameters['id']!; + // if "create", open empty form + if (id == 'create') { + return const SalesExecutive(); // create mode + } else { + return SalesExecutive(id: id); // update mode + } + }, + ), GoRoute( path: AppRoutes.enquiryLst, diff --git a/lib/core/routing/routes.dart b/lib/core/routing/routes.dart index 2fb2a3a..0609e46 100644 --- a/lib/core/routing/routes.dart +++ b/lib/core/routing/routes.dart @@ -8,6 +8,8 @@ class AppRoutes { static const agent = '/agent/:id'; static const String staffLst = '/staffLst'; static const staff = '/staff/:id'; + static const String salesExecutiveLst = '/salesExecutiveLst'; + static const salesExecutive = '/salesExecutive/:id'; static const String enquiryLst = '/enquiryLst'; static const String enquiryForStaff = '/enquiryForStaff'; static const String tabEnquiry = '/tabEnquiry'; diff --git a/lib/core/services/api_service.dart b/lib/core/services/api_service.dart index 7d21ae8..fa63ac8 100644 --- a/lib/core/services/api_service.dart +++ b/lib/core/services/api_service.dart @@ -428,7 +428,9 @@ class ApiService { }; final url; - if (role == 'staff') { + if (role == 'salesExecutive') { + url = Uri.parse('${Env.apiUrl}salesExecutive/changeExecutiveStatus'); + } else if (role == 'staff') { url = Uri.parse('${Env.apiUrl}staff/changeStaffStatus'); } else { url = Uri.parse('${Env.apiUrl}/api/agent/changeAgentStatus'); @@ -557,6 +559,23 @@ class ApiService { return response; } + Future> findProductivityDashboardData(id, broker) async { + // print(_token); + if (_token == null) { + await _initializeToken(); + } + final url = Uri.parse( + '${Env.apiUrl}dashboard/productivityDashboard?manager_id=$id', + ); + + final headers = { + 'Authorization': 'Bearer $_token' ?? '', + 'app-signature': Env.App_Signature, + }; + final response = await _makeGetRequest(url, headers); + return response; + } + // -------------------------------- AGENT ---------------------------------------------- Future> fetchAgentIncentiveList(agentId) async { @@ -705,6 +724,57 @@ class ApiService { final response = await _makeGetRequest(url, headers); return response; } + // ------------------------------------ SALES EXECUTIVE ----------------------------------------------------- + + Future> fetchSalesExecutiveList( + int managerId, + role, + ) async { + // print(_token); + + print('rolemanagerId - $managerId'); + if (_token == null) { + await _initializeToken(); + } + + dynamic url; + if (role == 'manager') { + print('manager'); + // url = Uri.parse('${Env.apiUrl}staff/staffList'); + url = Uri.parse( + '${Env.apiUrl}salesExecutive/executiveList?manager_id=${managerId}', + ); + // url = Uri.parse('${Env.apiUrl}salesExecutive/executiveList'); + } else { + print('handler'); + url = Uri.parse( + '${Env.apiUrl}salesExecutive/executiveList?handler_id=${managerId}', + ); + } + + final headers = { + 'Authorization': 'Bearer $_token' ?? '', + 'app-signature': Env.App_Signature, + }; + final response = await _makeGetRequest(url, headers); + return response; + } + + Future> findSingleSalesExecutiveData(id) async { + // print(_token); + if (_token == null) { + await _initializeToken(); + } + + final url = Uri.parse('${Env.apiUrl}salesExecutive/findExecutive?id=$id'); + // final url = Uri.parse('${Env.apiUrl}staff/findStaff?id=$id'); + final headers = { + 'Authorization': 'Bearer $_token' ?? '', + 'app-signature': Env.App_Signature, + }; + final response = await _makeGetRequest(url, headers); + return response; + } // ----------------------------------- ENQUIRY ------------------------------------------------- diff --git a/lib/presentation/layouts/appheader.dart b/lib/presentation/layouts/appheader.dart index 34a0a3b..93ce29b 100644 --- a/lib/presentation/layouts/appheader.dart +++ b/lib/presentation/layouts/appheader.dart @@ -56,7 +56,8 @@ class _AppHeaderState extends ConsumerState { } else if (currentRoute.contains('enquiry')) { _activeMenu = 'Enquiry'; } else if (currentRoute.contains('agent') || - currentRoute.contains('staff')) { + currentRoute.contains('staff') || + currentRoute.contains('salesExecutive')) { _activeMenu = 'User'; } else if (currentRoute.contains('claim') || currentRoute.contains('endosement') || @@ -145,6 +146,15 @@ class _AppHeaderState extends ConsumerState { context.go(AppRoutes.staffLst); }, ), + const SizedBox(height: 2), + _buildPopupItem( + label: "Sales Executive", + onTap: () { + _hidePopup(); + setState(() => _activeMenu = 'User'); + context.go(AppRoutes.salesExecutiveLst); + }, + ), ], if (key == 'Reports') ...[ if (role == 'manager') ...[ diff --git a/lib/presentation/screens/Enquiry/enquiry/policy_popup.dart b/lib/presentation/screens/Enquiry/enquiry/policy_popup.dart index 3e90f7e..57b25fc 100644 --- a/lib/presentation/screens/Enquiry/enquiry/policy_popup.dart +++ b/lib/presentation/screens/Enquiry/enquiry/policy_popup.dart @@ -481,8 +481,11 @@ class _AddDialogState extends State { onChanged: filterData, controller: _searchStaffController, txtwidth: ResponsiveLayout.isMobile(context) - ? MediaQuery.of(context).size.width * 0.7 - : MediaQuery.of(context).size.width * 0.15, + ? null + : MediaQuery.of(context).size.width * 0.26, + // txtwidth: ResponsiveLayout.isMobile(context) + // ? MediaQuery.of(context).size.width * 0.7 + // : MediaQuery.of(context).size.width * 0.15, ), const SizedBox(height: 16), diff --git a/lib/presentation/screens/UserManagement/Agent/agent.dart b/lib/presentation/screens/UserManagement/Agent/agent.dart index 9e94de3..3b175fb 100644 --- a/lib/presentation/screens/UserManagement/Agent/agent.dart +++ b/lib/presentation/screens/UserManagement/Agent/agent.dart @@ -1,6 +1,7 @@ import 'dart:convert'; // import 'dart:io' as html; import 'dart:typed_data'; // Import for Uint8List +import 'package:dropdown_search/dropdown_search.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -20,10 +21,13 @@ 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/userRoleProvider.dart'; import '../../../providers/user_provider.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/singleFileUpload.dart'; // import '../../../themes/indicators/upload_doc_theme.dart'; @@ -48,10 +52,19 @@ class AgentState extends ConsumerState { String? selectedId; PlatformFile? docUploadedFile; + final GlobalKey>> dropDownKey = + GlobalKey>>(); + Map controllers = {}; String? _token; dynamic userId; dynamic managerId; + dynamic role; + + bool isLoading = false; + String? selectedSalesExectv; + List> filteredSalesExecutiveData = []; + List> getSalesExecutiveData = []; Map dataDetails() { final data = { @@ -60,6 +73,7 @@ class AgentState extends ConsumerState { "mobile": controllers["mobile"]?.text, "address": controllers["address"]?.text, "agent_code": controllers["code"]?.text, + 'sales_executive_id': selectedSalesExectv, "is_active": isActive, "manager_id": managerId, }; @@ -79,6 +93,12 @@ class AgentState extends ConsumerState { Future.microtask(() { managerId = ref.watch(managerIdProvider); userId = ref.watch(userIdProvider); + + role = ref.read(userRoleProvider); + print("E43 => mId: $managerId"); + if (managerId != null && role != null) { + getRole(managerId, role); + } }); } @@ -87,6 +107,41 @@ class AgentState extends ConsumerState { print("APISERTOKEN - $_token"); } + Future getRole(int id, role) async { + print('getSalesExecutiveData called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchSalesExecutiveList(id, role); + + if (response['status'] == 'success') { + print('getRole - ${response['data']}'); + setState(() { + getSalesExecutiveData = List>.from( + response['data'], + ); + print('API Data - $getSalesExecutiveData'); + filteredSalesExecutiveData = getSalesExecutiveData + .where((item) => int.tryParse(item['is_active'].toString()) == 1) + .toList(); + + print('filteredSalesExecutiveData - $filteredSalesExecutiveData'); + }); + } else { + getSalesExecutiveData = []; + filteredSalesExecutiveData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + void updateData() async { if (widget.id != null && widget.id != 'create') { dynamic response = await apiService.findSingleAgentData(widget.id!); @@ -102,6 +157,7 @@ class AgentState extends ConsumerState { controllers['code']?.text = data['agent_code'] ?? ''; controllers['address']?.text = data['address'] ?? ''; isActive = data["is_active"]; + selectedSalesExectv = data['sales_executive_id'] ?? ''; String? apiDocPath = data["certificate_file_name"]; if (apiDocPath != null && apiDocPath.isNotEmpty) { @@ -470,12 +526,19 @@ class AgentState extends ConsumerState { SizedBox(height: 20), Row( children: [ - Expanded(child: buildAddress()), + Expanded(child: buildSalesExecutive(context)), SizedBox(width: 25), - Expanded(child: buildUploadDocument()), + Expanded(child: buildAddress()), ], ), SizedBox(height: 20), + Row( + children: [ + Expanded(child: buildUploadDocument()), + SizedBox(width: 25), + Expanded(child: SizedBox.shrink()), + ], + ), // Row( // children: [ // Expanded(child: buildIncentiveFile()), @@ -595,6 +658,157 @@ class AgentState extends ConsumerState { borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), txtwidth: MediaQuery.of(context).size.width * 0.26, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[ a-zA-Z0-9_#/.]')), + ], + ), + ], + ); + } + + Widget buildSalesExecutive(BuildContext context) { + Map? selectedroleVal = filteredSalesExecutiveData + .firstWhere( + (item) => item['id'].toString() == selectedSalesExectv, + orElse: () => {}, + ); + + final isReadOnly = widget.id != null; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Sales Executive *", style: _textStyle), + SizedBox(width: 10), + Container( + decoration: BoxDecoration( + // color: Colors.white, + borderRadius: BorderRadius.circular(10.0), + ), + width: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.26, + // height: 40, + child: AbsorbPointer( + absorbing: isReadOnly, + child: DropdownSearch>( + key: dropDownKey, + selectedItem: selectedroleVal.isNotEmpty ? selectedroleVal : null, + items: (filter, infiniteScrollProps) { + return filteredSalesExecutiveData; + }, + + 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; + // }, + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Executive", + ).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: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE2E8F0), + width: 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( + autofocus: true, + decoration: InputDecoration( + contentPadding: EdgeInsets.all(3), + filled: true, + fillColor: Colors.white, + hintText: "Search Executive...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE2E8F0), + // color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE2E8F0), + // color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + enabled: widget.id == null, + ), + ), + + itemBuilder: (context, item, isDisabled, isSelected) { + return Container( + color: isSelected + ? Colors.blue.withOpacity(0.1) + : Colors.white, + padding: EdgeInsets.symmetric(horizontal: 5, vertical: 5), + child: Text( + item['name'].toString(), + style: GoogleFonts.inter( + fontSize: 11, + color: Colors.black87, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.normal, + ), + ), + ); + }, + // constraints: BoxConstraints(), + ), + + onChanged: widget.id != null + ? null + : (val) { + if (val != null) { + print("Selected Role : ${val['role']}"); + print("Id: ${val['id']}"); + selectedSalesExectv = val['id']; + } + + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + }, + ), + ), ), ], ); diff --git a/lib/presentation/screens/UserManagement/SalesExecutive/salesExecutive.dart b/lib/presentation/screens/UserManagement/SalesExecutive/salesExecutive.dart new file mode 100644 index 0000000..628dab6 --- /dev/null +++ b/lib/presentation/screens/UserManagement/SalesExecutive/salesExecutive.dart @@ -0,0 +1,928 @@ +import 'dart:convert'; +// import 'dart:io' as html; +import 'dart:typed_data'; // Import for Uint8List +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_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; +import 'package:nhance_partner/core/routing/routes.dart'; +import 'package:nhance_partner/data/utils/toastNotification.dart'; +import 'package:nhance_partner/presentation/themes/indicators/text_field_theme.dart'; + +import '../../../../core/config/env.dart'; +import '../../../../core/services/api_service.dart'; +import '../../../../data/services/auth_service.dart'; +import '../../../../data/utils/validators.dart'; +import '../../../layouts/main_layout.dart'; +import '../../../layouts/responsive_layout.dart'; +import '../../../providers/manager_provider.dart'; + +import 'package:universal_html/html.dart' as html; + +import '../../../themes/indicators/input_field_decoration.dart'; + +class SalesExecutive extends ConsumerStatefulWidget { + final String? id; + const SalesExecutive({super.key, this.id}); + @override + ConsumerState createState() => SalesExecutiveState(); +} + +class SalesExecutiveState extends ConsumerState { + final _formKey = GlobalKey(); + late ApiService apiService; + + final GlobalKey>> dropDownKey = + GlobalKey>>(); + + final GlobalKey>> + dropDownKeyHandler = GlobalKey>>(); + + List tabHeader = ['name', 'email', 'mobile', 'code', 'address']; + late String isActive = "1"; + html.File? passportFile; + String? selectedFileNames; + String? passportFileUrlFromApi; + String? selectedId; + String? _token; + bool isLoading = false; + bool showHandler = false; + + String? selectedRole; + List> filteredRolesData = []; + List> getRolesData = []; + + List? selectedHandlerIds = []; + String? selectedHandler; + List> filteredHandlersData = []; + List> getHandlersData = []; + + Map controllers = {}; + + dynamic userId; + dynamic managerId; + + Map dataDetails() { + final data = { + "name": controllers["name"]?.text, + "email": controllers["email"]?.text, + "mobile": controllers["mobile"]?.text, + "address": controllers["address"]?.text, + // "emp_id": controllers["code"]?.text, + "is_active": isActive, + // "handler_id": selectedHandler, + "manager_id": userId, + }; + return data; + } + + @override + void initState() { + super.initState(); + apiService = ApiService(); + for (String field in tabHeader) { + controllers[field] = TextEditingController(); + } + + _initializeToken(); + updateData(); + getRole(); + + Future.microtask(() { + managerId = ref.watch(managerIdProvider); + userId = ref.watch(userIdProvider); + if (managerId != null) { + print('hansles'); + getHandlers(managerId); + } + }); + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + print("APISERTOKEN - $_token"); + } + + void updateData() async { + if (widget.id != null && widget.id != 'create') { + dynamic response = await apiService.findSingleSalesExecutiveData( + widget.id!, + ); + final data = response['data']; + print("updateData - ${response['data']}"); + + if (data == null) return; + setState(() { + selectedId = widget.id; + controllers['name']?.text = data['name'] ?? ''; + controllers['email']?.text = data['email'] ?? ''; + controllers['mobile']?.text = data['mobile'] ?? ''; + controllers['address']?.text = data['address'] ?? ''; + + // controllers['code']?.text = data['emp_id'] ?? ''; + isActive = data["is_active"]; + }); + } + } + + Future handleSave() async { + if (!_formKey.currentState!.validate()) return; + + final email = controllers['email']!.text.trim(); + final phone = controllers['mobile']!.text.trim(); + + // 🔴 Conditional validation: at least one required + if (email.isEmpty && phone.isEmpty) { + ToastHelper.showSuccessToast( + context, + 'Please enter either Email or Phone Number', + ); + return; + } + + setState(() { + if (_formKey.currentState!.validate()) { + dataDetails(); + final dataSet = dataDetails(); + print("dataSetAgent - $dataSet"); + createUserData(dataSet); + } else { + // isDisable = false; + } + }); + } + + Future createUserData(data) async { + final bool isUpdating = widget.id != null && widget.id != 'create'; + final String? id = isUpdating ? widget.id : null; + final String apiUrldata; + + apiUrldata = isUpdating + ? '${Env.apiUrl}salesExecutive/updateExecutive' + : '${Env.apiUrl}salesExecutive/createExecutive'; + + // final token = await getToken(); // Fetch token + + if (_token == null) { + throw Exception('Token not found. Please log in.'); + } + + if (isUpdating) { + data['id'] = id; // Add plan_id for update + data['updated_by'] = userId; + } else { + data['created_by'] = userId; + } + + print("data------- $data}"); + + try { + final response = await http.post( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $_token', + 'Content-Type': 'application/json', + 'app-signature': Env.App_Signature, + }, + body: jsonEncode(data), // Convert map to JSON + ); + + if (response.statusCode == 200) { + print("SalesExecutive submitted successfully!"); + print("Response: ${response.body}"); + + isUpdating + ? ToastHelper.showSuccessToast( + context, + 'SalesExecutive Updated Successfully', + ) + : ToastHelper.showSuccessToast( + context, + 'SalesExecutive Created Successfully', + ); + context.go(AppRoutes.salesExecutiveLst); + } else if (response.statusCode == 403) { + await apiService.clearLocalStorageAndRedirect(); + } else { + final responseBody = jsonDecode(response.body); + dynamic msg = responseBody['data']; + + print("Failed to submit plan. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("SalesExecutive User Creation Failed"), + content: Text(msg), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + } catch (e) { + print(" Error submitting SalesExecutive: $e"); + } + } + + Future getRole() async { + print('Handlers called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('staffRole'); + + if (response['status'] == 200) { + print('getRole - ${response['data']}'); + setState(() { + getRolesData = List>.from(response['data']); + print('API Data - $getRolesData'); + + filteredRolesData = List.from(getRolesData); + print('originalData - $filteredRolesData'); + }); + } else { + getRolesData = []; + filteredRolesData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future getHandlers(managerId) async { + print('Handlers called'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchHandlerNameDropDown(managerId); + + if (response['code'] == 200 || response['status'] == 'success') { + print('getHandlers -'); + print('getHandlers - ${response['data']}'); + setState(() { + getHandlersData = List>.from(response['data']); + print('API HandlersData - $getHandlersData'); + + filteredHandlersData = List.from(getHandlersData); + print('HandlersoriginalData - $filteredHandlersData'); + }); + } else { + getHandlersData = []; + filteredHandlersData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + @override + void dispose() { + for (var controller in controllers.values) { + controller.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MainLayout( + title: "SalesExecutive", + body: Container( + // color: Colors.yellow.shade50, + width: MediaQuery.of(context).size.width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + // height: 30, + // color: Colors.red.shade50, + width: MediaQuery.of(context).size.width, + child: GestureDetector( + onTap: () { + context.go(AppRoutes.staffLst); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Tooltip( + message: 'Back', + child: IconButton( + icon: const Icon( + Icons.arrow_left_sharp, + size: 25, + color: Color(0xFF425B5B), + ), + onPressed: () { + context.go(AppRoutes.salesExecutiveLst); + }, + splashRadius: 28, + hoverColor: Colors.black12, + padding: const EdgeInsets.all(8), + constraints: const BoxConstraints(), + ), + ), + const SizedBox(width: 5), // spacing between icon and text + Text( + "Sales Executive", + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + // SizedBox(height: 10), + Expanded( + child: Container( + // color: Colors.green, + width: MediaQuery.of(context).size.width, + margin: EdgeInsets.all(8.0), + padding: EdgeInsets.symmetric(horizontal: 14.0, vertical: 20.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + color: Colors.white, + // color: Color(0xFFEDF6F5), + ), + child: Column( + children: [ + Row(children: [Expanded(child: buildFormFields())]), + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () { + handleSave(); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 45.0, + vertical: 8, + ), + + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + color: Color(0xFF2E7D6E), + // color: Color(0xFF425B5B), + ), + child: Text( + 'Save', + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 12, + ), + ), + ), + ), + ], + ), + SizedBox(height: 30), + ], + ), + ), + ), + + SizedBox(height: 10), + ], + ), + ), + ); + } + + Widget buildFormFields() { + return Form( + key: _formKey, + child: Column( + children: [ + Row( + children: [ + Expanded(child: buildName()), + SizedBox(width: 25), + Expanded(child: buildEmail()), + ], + ), + SizedBox(height: 20), + Row( + children: [ + Expanded(child: buildPhNumber()), + SizedBox(width: 25), + Expanded(child: buildAddress()), + ], + ), + SizedBox(height: 20), + ], + ), + ); + } + + Widget buildName() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Name *", style: _textStyle), + SizedBox(width: 10), + ThemedFormField( + borderColor: Color(0xFFE2E8F0), + highlightColor: Color(0xFF50A398), + controller: controllers['name']!, + validator: (value) => Validators.requiredField(value, "name"), + txtwidth: MediaQuery.of(context).size.width * 0.26, + ), + ], + ); + } + + Widget buildEmail() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Email *", style: _textStyle), + SizedBox(width: 10), + ThemedFormField( + controller: controllers['email']!, + borderColor: Color(0xFFE2E8F0), + highlightColor: Color(0xFF50A398), + + // validator: (value) => Validators.email(value, "email"), + validator: (value) { + final phone = controllers['mobile']!.text.trim(); + + if (value!.isEmpty && phone.isNotEmpty) { + return null; // phone is given → email not required + } + + return Validators.email(value, "email"); + }, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')), + ], + txtwidth: MediaQuery.of(context).size.width * 0.26, + ), + ], + ); + } + + Widget buildPhNumber() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Phone Number *", style: _textStyle), + SizedBox(width: 10), + ThemedFormField( + controller: controllers['mobile']!, + borderColor: Color(0xFFE2E8F0), + highlightColor: Color(0xFF50A398), + + // validator: (value) => Validators.phone(value, "phNumber"), + validator: (value) { + final email = controllers['email']!.text.trim(); + + if (value!.isEmpty && email.isNotEmpty) { + return null; // email is given → phone not required + } + + return Validators.phone(value, "phNumber"); + }, + + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), + ], + txtwidth: MediaQuery.of(context).size.width * 0.26, + ), + ], + ); + } + + Widget buildAddress() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Address", style: _textStyle), + SizedBox(width: 10), + ThemedFormField( + controller: controllers['address']!, + borderColor: Color(0xFFE2E8F0), + highlightColor: Color(0xFF50A398), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[ a-zA-Z0-9_#/.]')), + ], + txtwidth: MediaQuery.of(context).size.width * 0.26, + ), + ], + ); + } + + Widget buildId() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("SalesExecutive Id ", style: _textStyle), + SizedBox(width: 10), + ThemedFormField( + controller: controllers['code']!, + borderColor: Color(0xFFE2E8F0), + highlightColor: Color(0xFF50A398), + // validator: (value) => Validators.requiredField(value, "id"), + txtwidth: MediaQuery.of(context).size.width * 0.26, + ), + ], + ); + } + + // Widget buildAddress() { + // return Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text("Role*", style: _textStyle), + // SizedBox(width: 10), + // ThemedFormField( + // controller: controllers['address']!, + // + // txtwidth: MediaQuery.of(context).size.width * 0.26, + // ), + // ], + // ); + // } + + Widget buildRole(BuildContext context) { + Map? selectedroleVal = filteredRolesData.firstWhere( + (item) => item['id'].toString() == selectedRole, + orElse: () => {}, + ); + + if (selectedroleVal.isNotEmpty && + selectedroleVal['role'] != null && + selectedroleVal['role'] == 'SalesExecutive') { + showHandler = true; + } else { + selectedHandlerIds = []; + showHandler = false; + } + + final isReadOnly = widget.id != null; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Role *", style: _textStyle), + SizedBox(width: 10), + Container( + decoration: BoxDecoration( + // color: Colors.white, + borderRadius: BorderRadius.circular(10.0), + ), + width: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.26, + // height: 40, + child: AbsorbPointer( + absorbing: isReadOnly, + child: DropdownSearch>( + key: dropDownKey, + selectedItem: selectedroleVal.isNotEmpty ? selectedroleVal : null, + items: (filter, infiniteScrollProps) { + return filteredRolesData; + }, + + itemAsString: (val) => val['role'].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; + }, + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Role", + ).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: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE2E8F0), + width: 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( + autofocus: true, + decoration: InputDecoration( + contentPadding: EdgeInsets.all(3), + filled: true, + fillColor: Colors.white, + hintText: "Search Role...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE2E8F0), + // color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE2E8F0), + // color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + enabled: widget.id == null, + ), + ), + // constraints: BoxConstraints(), + ), + + onChanged: widget.id != null + ? null + : (val) { + if (val != null) { + print("Selected Role : ${val['role']}"); + print("Id: ${val['id']}"); + selectedRole = val['id']; + if (val['role'] == 'SalesExecutive') { + setState(() { + showHandler = true; + }); + } else { + setState(() { + showHandler = false; + }); + } + + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ), + ], + ); + } + + Widget buildHandler(BuildContext context) { + Map? selectedHandlered = filteredHandlersData.firstWhere( + (item) => item['id'].toString() == selectedHandler, + orElse: () => {}, + ); + // final isReadOnly = widget.id != null; + final isReadOnly = false; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Handler Name *", style: _textStyle), + SizedBox(width: 10), + Container( + decoration: BoxDecoration( + // color: Colors.white, + borderRadius: BorderRadius.circular(10.0), + ), + width: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.26, + // height: 40, + child: AbsorbPointer( + absorbing: isReadOnly, + // child: DropdownSearch>( + child: DropdownSearch>.multiSelection( + key: dropDownKeyHandler, + + // selectedItem: selectedHandlered.isNotEmpty + // ? selectedHandlered + // : null, + selectedItems: filteredHandlersData + .where( + (item) => (selectedHandlerIds ?? []).contains(item['id']), + ) + .toList(), + items: (filter, infiniteScrollProps) { + return filteredHandlersData; + }, + + 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; + // }, + validator: (val) { + if (val == null || val.isEmpty) { + return "Required"; + } + return null; + }, + decoratorProps: DropDownDecoratorProps( + decoration: + AppInputDecorations.dropdownDecoration( + label: "Select Handler", + ).copyWith( + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + filled: true, + fillColor: + Colors.white, // 👈 makes the dropdown input white + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE2E8F0), + // color: Colors.white, + ), // 👈 Normal border + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE2E8F0), + // color: Colors.white, + width: 1.5, + ), // 👈 Focused border + ), + + contentPadding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + ), + ), + popupProps: PopupPropsMultiSelection.menu( + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + showSearchBox: true, + menuProps: MenuProps(backgroundColor: Colors.white), + + searchFieldProps: TextFieldProps( + autofocus: true, + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: "Search Handler...", + hintStyle: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + // color: Colors.blue, + color: Color(0xFFEDF6F5), + width: 1.5, + ), + ), + ), + ), + + // checkBoxBuilder: (context, item, isSelected, isDisabled) { + // return Icon( + // isSelected + // ? Icons.check_box + // : Icons.check_box_outline_blank, + // color: isSelected ? Color(0xFFE26728) : Colors.red, + // ); + // }, + // validationBuilder: (context, selectedItems) { + // return Padding( + // padding: const EdgeInsets.symmetric( + // vertical: 8.0, + // horizontal: 8.0, + // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [ + // ElevatedButton( + // style: ElevatedButton.styleFrom( + // backgroundColor: Color(0xFF425B5B), + // foregroundColor: Colors.white, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // minimumSize: Size(70, 36), + // ), + // onPressed: () => Navigator.pop(context), + // child: const Text("OK"), + // ), + // ], + // ), + // ); + // }, + ), + onChanged: (List> selectedVals) { + selectedHandlerIds = selectedVals + .map((v) => v['id'].toString()) + .toList(); + print("Selected Handler IDs: $selectedHandlerIds"); + }, + // 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 Handler...", + // enabledBorder: OutlineInputBorder( + // borderSide: BorderSide( + // color: Colors.white, + // ), // 👈 Normal border + // ), + // focusedBorder: OutlineInputBorder( + // borderSide: BorderSide( + // color: Colors.white, + // width: 1.5, + // ), // 👈 Focused border + // ), + // ), + // ), + // // constraints: BoxConstraints(), + // ), + // + // onChanged: (val) { + // if (val != null) { + // print("Selected Handler : ${val['name']}"); + // print("Id: ${val['id']}"); + // selectedHandler = val['id']; + // // controllers['agentId']?.text = val['agent_code']; + // // agentId = agent['id']; + // } + // }, + ), + ), + ), + ], + ); + } + + static final _textStyle = GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + ); +} diff --git a/lib/presentation/screens/UserManagement/SalesExecutive/salesExecutiveList.dart b/lib/presentation/screens/UserManagement/SalesExecutive/salesExecutiveList.dart new file mode 100644 index 0000000..e51c24e --- /dev/null +++ b/lib/presentation/screens/UserManagement/SalesExecutive/salesExecutiveList.dart @@ -0,0 +1,473 @@ +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:nhance_partner/presentation/providers/userRoleProvider.dart'; + +import '../../../../core/routing/routes.dart'; +import '../../../../core/services/api_service.dart'; +import '../../../../data/utils/Pagination.dart'; +import '../../../layouts/main_layout.dart'; +import '../../../layouts/responsive_layout.dart'; +import '../../../providers/manager_provider.dart'; +import '../../../themes/indicators/export_btn.dart'; +import '../../../themes/indicators/filter_btn.dart'; +import '../../../themes/indicators/search_field_theme.dart'; +import '../../../themes/indicators/text_field_theme.dart'; +import '../../../widgets/custom_action_popup.dart'; + +class SalesExecutiveList extends ConsumerStatefulWidget { + const SalesExecutiveList({super.key}); + @override + ConsumerState createState() => SalesExecutiveListState(); +} + +class SalesExecutiveListState extends ConsumerState { + int currentPage = 1; + int itemsPerPage = 10; + late ApiService apiService; + dynamic managerId; + dynamic role; + dynamic prefid; + // List> dataVal = []; + List> getStaffData = []; + List> originalData = []; + List> filteredData = []; + bool isLoading = false; + + @override + void initState() { + super.initState(); + + apiService = ApiService(); + + Future.microtask(() { + final data1 = ref.read(managerIdProvider); + // final data2 = ref.read(handlerIdProvider); + print("Edata1 => mId: $data1 -2 :"); + prefid = data1; + role = ref.read(userRoleProvider); + print("E43 => mId: $prefid"); + if (prefid != null && role != null) { + getSalesExecutiveList(prefid, role); + } + }); + // getSalesExecutiveList(); + } + + 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) { + setState(() { + final q = query.toLowerCase(); + + if (q.isEmpty) { + filteredData = getStaffData; + return; + } + + filteredData = getStaffData.where((item) { + final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive"; + + return (item['name'] ?? '').toString().toLowerCase().contains(q) || + (item['email'] ?? '').toString().toLowerCase().contains(q) || + (item['mobile'] ?? '').toString().toLowerCase().contains(q) || + (item['address'] ?? '').toString().toLowerCase().contains(q) || + (item['emp_id'] ?? item['agent_code'] ?? '') + .toString() + .toLowerCase() + .contains(q) || + isActiveStatus.contains(q); + }).toList(); + }); + } + + final TextEditingController _searchStaffController = TextEditingController(); + + Future getSalesExecutiveList(int id, role) async { + print('E104 => Fns called => $id'); + print('E104 => Fns role => $role'); + setState(() { + isLoading = true; + }); + + try { + final response = await apiService.fetchSalesExecutiveList(id, role); + + if (response['status'] == 'success') { + print('E113 => getSalesExecutiveListData => ${response['data']}'); + setState(() { + getStaffData = List>.from(response['data']); + originalData = getStaffData; + filteredData = List.from(originalData); + // print('originalData - $getClaimPolicies'); + }); + } else { + getStaffData = []; + originalData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + managerId = ref.watch(managerIdProvider); + + return MainLayout( + title: "Sales Executive", + body: Container( + // padding: EdgeInsets.all(8.0), + // margin: EdgeInsets.all(10.0), + + // color: Colors.yellow.shade50, + width: MediaQuery.of(context).size.width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Container( + // color: Colors.green, + // color: Colors.green.shade50, + width: MediaQuery.of(context).size.width, + // margin: EdgeInsets.all(10.0), + decoration: BoxDecoration( + // color: Colors.white, + borderRadius: BorderRadius.circular(15.0), + ), + + padding: EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + // height: 40, + // color: Colors.pink, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Sales Executive List', + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + Spacer(), + ThemedSearchField( + hintText: 'Search', + // backgroundColor: Color(0xFFF6F8F8), + backgroundColor: Color(0xFFFFFFFF), + txtHeight: 30, + onChanged: filterData, + controller: _searchStaffController, + txtwidth: MediaQuery.of(context).size.width * 0.15, + ), + + SizedBox(width: 10), + ExportBtn( + sheetName: "SalesExecutive", + fileName: "salesExecutive_list", + txt: !ResponsiveLayout.isMobile(context) + ? true + : false, + data: filteredData, + displayHeaders: [ + 'S.No.', + 'Staff Name', + 'Email', + 'Phone Number', + 'Address', + 'Status', + ], + keys: [ + "sno", // handled internally as i + 1 + "name", + "email", + "mobile", + "address", + "is_active", + ], + ), + + SizedBox(width: 10), + InkWell( + onTap: () { + context.go('/salesExecutive/create'); + }, + child: Container( + padding: EdgeInsets.all(4.8), + decoration: BoxDecoration( + color: const Color(0xFF2E7D6E), + borderRadius: BorderRadius.circular(8.0), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Tooltip( + message: 'Create Sales Executive', + child: Icon( + Icons.add, + color: Colors.white, + size: 18, + ), + ), + ], + ), + ), + ), + ], + ), + ), + SizedBox(height: 5), + Container( + decoration: BoxDecoration( + color: Color(0xFFF1F5F9), + + // color: Color(0xFFEDF6F5), + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 16, + ), + child: Row( + children: [ + Expanded( + flex: 1, + child: Text('S.No.', style: _headerStyle), + ), + Expanded( + flex: 2, + child: Text('Executive Name', style: _headerStyle), + ), + + Expanded( + flex: 3, + child: Text('Email', style: _headerStyle), + ), + Expanded( + flex: 2, + child: Text('Phone Number', style: _headerStyle), + ), + + Expanded( + flex: 1, + child: Text('Status', style: _headerStyle), + ), + Expanded( + flex: 1, + child: Text('Action', style: _headerStyle), + ), + ], + ), + ), + Expanded(child: _buildDataTable(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; + }); + }, + ), + ), + ], + ), + ), + ); + } + + Widget _buildDataTable(BuildContext context) { + if (filteredData.isEmpty) { + return const SizedBox( + height: 50, + child: Center(child: Text('No available data')), + ); + } + + final sortedData = [..._paginatedData]; + + return ListView.builder( + // itemCount: filteredData.length + 1, // +1 for header, +1 for pagination + itemCount: sortedData.length + 1, // +1 for header, +1 for pagination + itemBuilder: (context, index) { + if (index == 0) return _buildHeader(); + // if (index == dataVal.length + 1) + // return _buildPagination(context); + final startIndex = ((currentPage - 1) * itemsPerPage); + // final item = filteredData[index - 1]; + final item = sortedData[index - 1]; + final sno = startIndex + index; + return _buildDataRow(item, sno); + }, + ); + } + + Widget _buildHeader() { + return SizedBox.shrink(); + } + + Widget _buildDataRow(Map item, sno) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 16), + // margin: const EdgeInsets.only(top: 10), + decoration: BoxDecoration( + color: Colors.white, + // color: Color(0xFFE0F7F9), + border: const Border( + bottom: BorderSide(color: Colors.blueGrey, width: 0.15), + ), + // borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)), + Expanded(flex: 2, child: Text(item['name'] ?? '-', style: _dataBold)), + Expanded( + flex: 3, + child: Text(item['email'] ?? '-', style: _dataBold), + ), + Expanded( + flex: 2, + child: Text(item['mobile'] ?? '-', style: _dataBold), + ), + + Expanded( + flex: 1, + child: Row( + children: [ + Container( + // color: Colors.yellow.shade50, + child: Transform.scale( + scale: 0.4, // reduce size (0.7–0.9 works well) + child: Switch( + value: item['is_active'] == "1", + onChanged: (val) { + setState(() { + item['is_active'] = val ? "1" : "0"; + }); + final response = apiService.updateStatus( + item['id'], + val ? "0" : "1", + 'salesExecutive', + ); + print("Response - $response"); + }, + activeColor: Color(0xFF2E7D6E), // thumb when active + activeTrackColor: Color(0xFFDCFCE7), // track when active + // activeColor: Color(0xFF425B5B), // thumb when active + // activeTrackColor: Color(0xFFB2D8D3), // track when active + inactiveThumbColor: + Colors.grey.shade400, // thumb when inactive + inactiveTrackColor: + Colors.grey.shade300, // track when inactive + ), + ), + ), + ], + ), + ), + + Expanded( + flex: 1, + child: item['is_active'] == "1" + ? Row( + children: [ + Tooltip( + message: 'Edit', + child: IconButton( + icon: Image.asset( + "assets/miscellaneous/Edit.png", + height: 12, + width: 15, + ), + onPressed: () { + print('EDITStaff - ${item['id']}'); + dynamic id = item['id']; + context.go('/salesExecutive/$id'); + }, + splashRadius: 28, + hoverColor: Colors.black12, + padding: const EdgeInsets.all(8), + constraints: const BoxConstraints(), + ), + ), + ], + ) + : Row( + children: [ + Image.asset( + "assets/miscellaneous/Edit_muted.png", + height: 12, + width: 15, + ), + ], + ), + ), + ], + ), + ); + } + + static final _dataBold = GoogleFonts.inter( + fontSize: 12, + + fontWeight: FontWeight.w400, + color: Color(0xFF000000), + ); + + static final _dataSub = GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w300, + color: Color(0xFF585757), + ); + + static final _headerStyle = GoogleFonts.poppins( + fontSize: 11.2, + fontWeight: FontWeight.w500, + color: Color(0xFF1E293B), + ); +} diff --git a/lib/presentation/screens/dashboard/dashboard_nw.dart b/lib/presentation/screens/dashboard/dashboard_nw.dart index d2b4a74..e01a399 100644 --- a/lib/presentation/screens/dashboard/dashboard_nw.dart +++ b/lib/presentation/screens/dashboard/dashboard_nw.dart @@ -8,6 +8,7 @@ import 'package:nhance_partner/presentation/screens/dashboard/tabs/chart.dart'; import 'package:nhance_partner/presentation/screens/dashboard/tabs/live.dart'; import 'package:nhance_partner/presentation/screens/dashboard/tabs/partner.dart'; import 'package:nhance_partner/presentation/screens/dashboard/tabs/productivity.dart'; +import 'package:nhance_partner/presentation/screens/dashboard/tabs/stacked_BAR_Exple.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../../core/responsive/responsive_builder.dart'; import '../../../core/routing/routes.dart'; @@ -135,7 +136,8 @@ class _DashboardState extends ConsumerState LiveDashboard(), BussinessDashboard(), PartnerDashboard(), - ProductiviyDashboard(), + ProductivityDashboard(), + // BarChartSample4(), ], ), ), diff --git a/lib/presentation/screens/dashboard/tabs/bussiness.dart b/lib/presentation/screens/dashboard/tabs/bussiness.dart index 3d6f20b..5cf003f 100644 --- a/lib/presentation/screens/dashboard/tabs/bussiness.dart +++ b/lib/presentation/screens/dashboard/tabs/bussiness.dart @@ -275,38 +275,69 @@ class _BussinessDashboardState extends ConsumerState { child: Column( children: [ Row( - crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, - // mainAxisSize: MainAxisSize.min, children: [ Text('Hide and Show Charts : ', style: _styleSmall1), - TextButton( - onPressed: () { - setState(() => showInsurer = !showInsurer); - }, - child: Text( - showInsurer ? "Insurer" : "Insurer Hidden", - style: _styleSmall2, - ), + Row( + children: [ + Transform.scale( + scale: 0.75, + child: Checkbox( + value: showInsurer, + onChanged: (val) { + setState(() => showInsurer = val!); + }, + ), + ), + Text( + // showInsurer ? "Insurer" : "Insurer Hidden", + "Insurer", + style: _styleSmall2, + ), + ], ), - TextButton( - onPressed: () { - setState(() => showBroker = !showBroker); - }, - child: Text( - showBroker ? "Broker" : "Broker Hidden", - style: _styleSmall2, - ), + + SizedBox(width: 10), + + Row( + children: [ + Transform.scale( + scale: 0.75, + child: Checkbox( + value: showBroker, + onChanged: (val) { + setState(() => showBroker = val!); + }, + ), + ), + Text( + // showBroker ? "Broker" : "Broker Hidden", + "Broker", + style: _styleSmall2, + ), + ], ), - TextButton( - onPressed: () { - setState(() => showProduct = !showProduct); - }, - child: Text( - showProduct ? "Product " : "Product Hidden", - style: _styleSmall2, - ), + + SizedBox(width: 10), + + Row( + children: [ + Transform.scale( + scale: 0.75, + child: Checkbox( + value: showProduct, + onChanged: (val) { + setState(() => showProduct = val!); + }, + ), + ), + Text( + // showProduct ? "Product" : "Product Hidden", + "Product", + style: _styleSmall2, + ), + ], ), ], ), @@ -334,6 +365,7 @@ class _BussinessDashboardState extends ConsumerState { return Container( padding: EdgeInsets.all(10), margin: EdgeInsets.only(right: 20), + clipBehavior: Clip.none, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), @@ -364,9 +396,13 @@ class _BussinessDashboardState extends ConsumerState { ), SizedBox(height: 10), Expanded( - child: CustomBarChart( - dataList: chartInsurerData, - labels: chartInsurerLabels, + child: Container( + clipBehavior: Clip.none, + // color: Colors.red.shade100, + child: CustomBarChart( + dataList: chartInsurerData, + labels: chartInsurerLabels, + ), ), ), legendWidget(), diff --git a/lib/presentation/screens/dashboard/tabs/productivity.dart b/lib/presentation/screens/dashboard/tabs/productivity.dart index c0c481b..a75110b 100644 --- a/lib/presentation/screens/dashboard/tabs/productivity.dart +++ b/lib/presentation/screens/dashboard/tabs/productivity.dart @@ -9,52 +9,51 @@ import '../../../../core/services/api_service.dart'; import '../../../../data/models/bar_data.dart'; import '../../../providers/userRoleProvider.dart'; import '../../../themes/charts/barChart.dart'; +import '../../../themes/charts/stackedBarChart.dart'; -class ProductiviyDashboard extends ConsumerStatefulWidget { - const ProductiviyDashboard({super.key}); +class ProductivityDashboard extends ConsumerStatefulWidget { + const ProductivityDashboard({super.key}); @override - ConsumerState createState() => - _ProductiviyDashboardState(); + ConsumerState createState() => + _ProductivityDashboardState(); } -class _ProductiviyDashboardState extends ConsumerState { +class _ProductivityDashboardState extends ConsumerState { dynamic managerId; late ApiService apiService; // List> dataVal = []; - List> getBusinessDshBdData = []; + List> getProductivityDshBdData = []; List> originalData = []; List> filteredData = []; - List> brokerWise = []; List> productWise = []; List> insurerWise = []; - List> originalBrokerWise = []; - List> filteredBrokerWise = []; - List> originalProductWise = []; List> filteredProductWise = []; - List> originalInsurerWise = []; - List> filteredInsurerWise = []; + List> originalStaffWise = []; + List> filteredStaffWise = []; bool isLoading = false; bool isInsureWisePolicy = true; - bool isBrokerWisePolicy = true; bool isProductWisePolicy = true; - bool showInsurer = true; - bool showBroker = true; + bool showStaff = true; bool showProduct = true; + List staffLabels = []; + List>> staffChartData = []; + double maxPremium = 0; + int selectedView = 0; - // 0 = Insurer + // 0 = Staff // 1 = Broker // 2 = Product - String? SelctdBusinessbroker; + String? SelctdProductivityproduct; @override void initState() { @@ -70,54 +69,115 @@ class _ProductiviyDashboardState extends ConsumerState { // print("ENQroleId - $roleId"); if (managerId != null) { - getBusinessDashBrdList(managerId); + getProductivityDashBrdList(managerId); } }); } - Future getBusinessDashBrdList(id) async { + Future getProductivityDashBrdList(id) async { // print('getClaimList called MagId - $managerId - $role'); setState(() { isLoading = true; }); // dynamic id = widget.policyNumber; - print('getBusinessDshID - $id'); + print('getProductivityDshID - $id'); try { - final response = await apiService.findBusinessDashboardData( + final response = await apiService.findProductivityDashboardData( id, - SelctdBusinessbroker, + SelctdProductivityproduct, ); if (response['status'] == 'success') { final data = response['data']; - print('getBusinessDshBdData - ${response['data']}'); + print('getProductivityDshBdData - ${response['data']}'); setState(() { - if (data is List) { - // Already a list of maps - getBusinessDshBdData = List>.from(data); - } else if (data is Map) { - // Single object, wrap in a list - getBusinessDshBdData = [Map.from(data)]; - } else { - getBusinessDshBdData = []; + if (data is Map) { + staffLabels.clear(); + staffChartData.clear(); + maxPremium = 0; + + final staffWise = (data['StaffWise'] ?? []) as List; + + staffLabels.clear(); + staffChartData.clear(); + maxPremium = 0.0; + + double _toDouble(dynamic v) { + if (v == null) return 0.0; + if (v is double) return v; + if (v is int) return v.toDouble(); + return double.tryParse(v.toString()) ?? 0.0; + } + + for (var staff in staffWise) { + final name = staff['sales_executive_name']?.toString() ?? ''; + staffLabels.add(name); + + double totalPre = 0.0; + double totalCur = 0.0; + + final products = (staff['products_list'] ?? []) as List; + for (var p in products) { + totalPre += _toDouble(p['total_premium_pre_month']); + totalCur += _toDouble(p['total_premium_current_month']); + } + + // build 3 stacks: preMonth, currentMonth (stacked after pre), extra (difference optional) + final preStart = 0.0; + final preEnd = totalPre; + final curStart = preEnd; + final curEnd = preEnd + totalCur; + // optional extra stack (here zero, but kept for structure) + final extraStart = curEnd; + final extraEnd = curEnd; // no extra by default + + // update chart max + maxPremium = [maxPremium, curEnd].reduce((a, b) => a > b ? a : b); + + staffChartData.add([ + [preStart, preEnd, Colors.cyan], + [curStart, curEnd, Colors.blueAccent], + // include extra only if needed >0 + if ((extraEnd - extraStart) > 0) + [extraStart, extraEnd, Colors.grey], + ]); + } + + // use setState to save and pass to chart + setState(() { + originalStaffWise = List.from(staffWise); // if you need + staffLabels = staffLabels; + staffChartData = staffChartData; + // choose a little padding for maxY + final chartMax = (maxPremium * 1.1).ceilToDouble(); + // store chartMax somewhere e.g. chartMaxY + }); + + setState(() {}); } - // getBusinessDshBdData = List>.from(response['data']); - originalData = getBusinessDshBdData; - filteredData = List.from(originalData); - // print('originalData - $getClaimPolicies'); + + // if (data is List) { + // // Already a list of maps + // getProductivityDshBdData = List>.from(data); + // } else if (data is Map) { + // // Single object, wrap in a list + // getProductivityDshBdData = [Map.from(data)]; + // } else { + // getProductivityDshBdData = []; + // } + // // getProductivityDshBdData = List>.from(response['data']); + // originalData = getProductivityDshBdData; + // filteredData = List.from(originalData); + // // print('originalData - $getClaimPolicies'); }); if (data is Map) { setState(() { // Store BrokerWise - brokerWise = List>.from( - data['brokerWise'] ?? [], + productWise = List>.from( + data['productWise'] ?? [], ); - print('brokerWise - ${brokerWise}'); - originalBrokerWise = List.from(brokerWise); - filteredBrokerWise = List.from(brokerWise); - // Store ProductWise productWise = List>.from( data['productWise'] ?? [], @@ -127,22 +187,22 @@ class _ProductiviyDashboardState extends ConsumerState { originalProductWise = List.from(productWise); filteredProductWise = List.from(productWise); - // Store InsurerWise + // Store StaffWise insurerWise = List>.from( data['insurerWise'] ?? [], ); print('insurerWise - ${insurerWise}'); - originalInsurerWise = List.from(insurerWise); - filteredInsurerWise = List.from(insurerWise); + originalStaffWise = List.from(insurerWise); + filteredStaffWise = List.from(insurerWise); }); } else { // fallback - brokerWise = []; + productWise = []; productWise = []; insurerWise = []; } } else { - getBusinessDshBdData = []; + getProductivityDshBdData = []; originalData = []; } } catch (e) { @@ -154,7 +214,7 @@ class _ProductiviyDashboardState extends ConsumerState { } } - List buildInsurerBarData() { + List buildStaffBarData() { double parseDouble(dynamic value) { if (value == null) return 0.0; if (value is num) return value.toDouble(); @@ -162,7 +222,7 @@ class _ProductiviyDashboardState extends ConsumerState { return 0.0; } - return filteredInsurerWise.map((item) { + return filteredStaffWise.map((item) { print('policies_current - ${item['total_policies_current_month']}'); print('policies_PREV - ${item['total_policies_pre_month']}'); @@ -181,8 +241,8 @@ class _ProductiviyDashboardState extends ConsumerState { }).toList(); } - List buildInsurerLabels() { - return filteredInsurerWise.map((item) { + List buildStaffLabels() { + return filteredStaffWise.map((item) { final name = item['short_name']; if (name != null && name.toString().trim().isNotEmpty) { return name.toString(); @@ -192,44 +252,6 @@ class _ProductiviyDashboardState extends ConsumerState { }).toList(); } - List buildBrokerBarData() { - double parseDouble(dynamic value) { - if (value == null) return 0.0; - if (value is num) return value.toDouble(); - if (value is String) return double.tryParse(value) ?? 0.0; - return 0.0; - } - - return filteredBrokerWise.map((item) { - print('policies_current - ${item['total_policies_current_month']}'); - print('policies_PREV - ${item['total_policies_pre_month']}'); - - print('premium_current - ${item['total_premium_current_month']}'); - print('premium_PREV - ${item['total_premium_pre_month']}'); - return BarData( - Colors.blue, - isBrokerWisePolicy - ? parseDouble(item['total_policies_current_month']) - : parseDouble(item['total_premium_current_month']), - isBrokerWisePolicy - ? parseDouble(item['total_policies_pre_month']) - : parseDouble(item['total_premium_pre_month']), - 5, - ); - }).toList(); - } - - List buildBrokerLabels() { - return filteredBrokerWise.map((item) { - final name = item['broker_name']; - if (name != null && name.toString().trim().isNotEmpty) { - return name.toString(); - } else { - return item['broker_name']?.toString() ?? ''; - } - }).toList(); - } - List buildProductBarData() { double parseDouble(dynamic value) { if (value == null) return 0.0; @@ -274,12 +296,196 @@ class _ProductiviyDashboardState extends ConsumerState { margin: EdgeInsets.only(left: 10, right: 10, bottom: 10), // color: Colors.amber.shade100, child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [Text('No Data Found ', style: _styleSmall1)], + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Hide and Show Charts : ', style: _styleSmall1), + + Row( + children: [ + Transform.scale( + scale: 0.75, + child: Checkbox( + value: showStaff, + onChanged: (val) { + setState(() => showStaff = val!); + }, + ), + ), + Text( + // showStaff ? "Staff" : "Staff Hidden", + "Staff", + style: _styleSmall2, + ), + ], + ), + + // SizedBox(width: 10), + // + // Row( + // children: [ + // Transform.scale( + // scale: 0.75, + // child: Checkbox( + // value: showProduct, + // onChanged: (val) { + // setState(() => showProduct = val!); + // }, + // ), + // ), + // Text( + // // showBroker ? "Broker" : "Broker Hidden", + // "Product", + // style: _styleSmall2, + // ), + // ], + // ), + ], + ), + + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + showStaff ? Expanded(child: insurerContainer()) : SizedBox(), + // showProduct ? Expanded(child: productContainer()) : SizedBox(), + ], + ), + ), + ], ), ); } + + Widget insurerContainer() { + final chartStaffData = buildStaffBarData(); + final chartStaffLabels = buildStaffLabels(); + print('chartStaffData - $chartStaffData'); + print('chartStaffLabels - $chartStaffLabels'); + return Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.only(right: 20), + clipBehavior: Clip.none, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + 'Staff Wise (Previous / Current Month)', + style: _chartHeader, + ), + ), + Text( + isInsureWisePolicy ? "Policies" : "Premium", + style: _styleSmall1, + ), + Transform.scale( + scale: 0.5, + child: Switch( + value: isInsureWisePolicy, + onChanged: (val) => setState(() => isInsureWisePolicy = val), + ), + ), + ], + ), + SizedBox(height: 10), + + // Expanded( + // child: Container( + // clipBehavior: Clip.none, + // // color: Colors.red.shade100, + // child: CustomStackedBarChart( + // labels: staffLabels, // 👈 staff names + // maxY: maxPremium + 5000, // avoid touching border + // data: staffChartData, + // ), + // ), + // ), + Expanded( + child: CustomStackedBarChart( + labels: staffLabels, + data: staffChartData, + maxY: (maxPremium * 1.1).ceilToDouble(), // a bit of headroom + initialRotation: 1, + ), + ), + + legendWidget(), + ], + ), + ); + } + + Widget productContainer() { + final chartProductData = buildProductBarData(); + final chartProductLabels = buildProductLabels(); + print('chartProductData - $chartProductData'); + print('chartProductLabels - $chartProductLabels'); + return Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.only(right: 20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + 'Staff Product Wise (Previous / Current Month)', + style: _chartHeader, + ), + ), + Text( + isProductWisePolicy ? "Policies" : "Premium", + style: _styleSmall1, + ), + Transform.scale( + scale: 0.5, + child: Switch( + value: isProductWisePolicy, + onChanged: (val) => setState(() => isProductWisePolicy = val), + ), + ), + ], + ), + SizedBox(height: 10), + Expanded( + child: CustomBarChart( + dataList: chartProductData, + labels: chartProductLabels, + ), + ), + legendWidget(), + ], + ), + ); + } + + Widget legendWidget() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.circle, size: 10, color: Colors.cyan.shade300), + SizedBox(width: 2), + Text('Current Month', style: _styleSmall1), + SizedBox(width: 5), + Icon(Icons.circle, size: 10, color: Colors.orange.shade300), + SizedBox(width: 2), + Text('Previous Month', style: _styleSmall1), + ], + ); + } } final _headerStyle2 = GoogleFonts.poppins( diff --git a/lib/presentation/screens/dashboard/tabs/stacked_BAR_Exple.dart b/lib/presentation/screens/dashboard/tabs/stacked_BAR_Exple.dart new file mode 100644 index 0000000..652e011 --- /dev/null +++ b/lib/presentation/screens/dashboard/tabs/stacked_BAR_Exple.dart @@ -0,0 +1,177 @@ +import 'package:fl_chart/fl_chart.dart'; + +import 'package:flutter/material.dart'; + +import '../../../themes/charts/appChartColors.dart'; + +class BarChartSample4 extends StatefulWidget { + BarChartSample4({super.key}); + + final Color dark = Colors.blueAccent; + final Color normal = AppColors.contentColorCyan; + final Color light = Colors.limeAccent; + + @override + State createState() => BarChartSample4State(); +} + +class BarChartSample4State extends State { + int rotationTurns = 1; + Widget bottomTitles(double value, TitleMeta meta) { + const style = TextStyle(fontSize: 10); + String text = switch (value.toInt()) { + 0 => 'Apr', + 1 => 'May', + 2 => 'Jun', + 3 => 'Jul', + 4 => 'Aug', + _ => '', + }; + return SideTitleWidget( + meta: meta, + child: Text(text, style: style), + ); + } + + Widget leftTitles(double value, TitleMeta meta) { + if (value == meta.max) { + return Container(); + } + const style = TextStyle(fontSize: 10); + return SideTitleWidget( + meta: meta, + child: Text(meta.formattedValue, style: style), + ); + } + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 1.66, + child: Padding( + padding: const EdgeInsets.only(top: 16), + child: LayoutBuilder( + builder: (context, constraints) { + final barsSpace = 4.0 * constraints.maxWidth / 400; + final barsWidth = 8.0 * constraints.maxWidth / 400; + return BarChart( + BarChartData( + alignment: BarChartAlignment.center, + rotationQuarterTurns: rotationTurns, + barTouchData: BarTouchData(enabled: false), + + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 28, + getTitlesWidget: bottomTitles, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: leftTitles, + ), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + gridData: FlGridData( + show: true, + checkToShowHorizontalLine: (value) => value % 10 == 0, + getDrawingHorizontalLine: (value) => FlLine( + color: AppColors.borderColor.withValues(alpha: 0.1), + strokeWidth: 1, + ), + drawVerticalLine: false, + ), + borderData: FlBorderData(show: false), + groupsSpace: barsSpace, + barGroups: getData(barsWidth), + ), + ); + }, + ), + ), + ); + } + + List getData(double barsWidth) { + return [ + /// -------------------- 1st Group -------------------- + BarChartGroupData( + x: 0, + barRods: [ + /// Last Month + BarChartRodData( + toY: 20000000000, + rodStackItems: [ + BarChartRodStackItem(0, 3000000000, Colors.cyan), + BarChartRodStackItem(3000000000, 11000000000, Colors.blueAccent), + BarChartRodStackItem(11000000000, 20000000000, Colors.grey), + ], + width: barsWidth, + borderRadius: BorderRadius.zero, + ), + + /// Current Month + BarChartRodData( + toY: 24000000000, + rodStackItems: [ + BarChartRodStackItem(0, 2000000000, Colors.redAccent), + BarChartRodStackItem(2000000000, 11000000000, Colors.greenAccent), + BarChartRodStackItem( + 11000000000, + 24000000000, + Colors.purpleAccent, + ), + ], + width: barsWidth, + borderRadius: BorderRadius.zero, + ), + ], + ), + + /// -------------------- 2nd Group -------------------- + BarChartGroupData( + x: 1, + barRods: [ + /// Last Month + BarChartRodData( + toY: 28000000000, + rodStackItems: [ + BarChartRodStackItem(0, 8000000000, Colors.cyan), + BarChartRodStackItem(8000000000, 22000000000, Colors.blueAccent), + BarChartRodStackItem(22000000000, 28000000000, Colors.grey), + ], + width: barsWidth, + borderRadius: BorderRadius.zero, + ), + + /// Current Month + BarChartRodData( + toY: 34000000000, + rodStackItems: [ + BarChartRodStackItem(0, 8000000000, Colors.redAccent), + BarChartRodStackItem(8000000000, 27000000000, Colors.greenAccent), + BarChartRodStackItem( + 27000000000, + 34000000000, + Colors.purpleAccent, + ), + ], + width: barsWidth, + borderRadius: BorderRadius.zero, + ), + ], + ), + ]; + } +} diff --git a/lib/presentation/screens/staff/Enquiry/Proposal_QuickCreation/Proposal_QuickCreation.dart b/lib/presentation/screens/staff/Enquiry/Proposal_QuickCreation/Proposal_QuickCreation.dart index 54f8a0b..f9aefb3 100644 --- a/lib/presentation/screens/staff/Enquiry/Proposal_QuickCreation/Proposal_QuickCreation.dart +++ b/lib/presentation/screens/staff/Enquiry/Proposal_QuickCreation/Proposal_QuickCreation.dart @@ -30,24 +30,7 @@ import '../../../../themes/indicators/text_field_theme_inline_editor.dart' hide UpperCaseTextFormatter; class CreateProposal_Quick extends ConsumerStatefulWidget { - // final dynamic userId; - // final dynamic managerId; - // final dynamic selectedEnquiryId; - // final dynamic selectedInsurdId; - // final dynamic selectedQuotationFrmListdata; - // final dynamic selectedQuotationFrmListId; - // final void Function(String value) onSubmit; - - const CreateProposal_Quick({ - super.key, - // required this.userId, - // required this.managerId, - // required this.selectedEnquiryId, - // required this.selectedInsurdId, - // this.selectedQuotationFrmListdata, - // this.selectedQuotationFrmListId, - // required this.onSubmit, - }); + const CreateProposal_Quick({super.key}); @override ConsumerState createState() => diff --git a/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart index f72bc4c..cd42212 100644 --- a/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart +++ b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart @@ -3526,9 +3526,9 @@ class EnquiryStaffState extends ConsumerState { // 'Proposal Rejected': 'Awaiting Payment', // 'Policy Created': 'Policy Created', // 'Policy Created': 'Policy Created', - 'To be assigned': 'To be assigned', + 'To be assigned': 'To Be Assigned', 'Assigned': 'Assigned', - 'In progress': 'In progress', + 'In progress': 'In Progress', }; List> get filteredDataByTab { @@ -3654,7 +3654,7 @@ class EnquiryStaffState extends ConsumerState { SizedBox(width: 8), Text( // '${config['icon']} $status', - '$status', + _formatStatus(status), style: GoogleFonts.inter( fontSize: 13, color: Colors.black, @@ -4000,7 +4000,9 @@ class EnquiryStaffState extends ConsumerState { SizedBox( width: 60, child: Text( - item['insurer_short_name'] ?? '-', + item['insurer_short_name'] != null + ? item['insurer_short_name'] + : '-', style: _dataBoldthm2, ), ), @@ -4227,7 +4229,8 @@ class EnquiryStaffState extends ConsumerState { ), ), - if (item['enquiry_status'] != 'Completed') ...[ + if (item['enquiry_status'] != 'Completed' && + roleId == 'manager') ...[ IconButton( padding: EdgeInsets.zero, // remove outer padding constraints: BoxConstraints( @@ -5405,6 +5408,17 @@ class EnquiryStaffState extends ConsumerState { fontSize: 12, ); + String _formatStatus(String status) { + return status + .split(' ') + .map( + (word) => word.isNotEmpty + ? word[0].toUpperCase() + word.substring(1).toLowerCase() + : '', + ) + .join(' '); + } + String _truncateText(String text, int limit) { if (text.length <= limit) return text; return '${text.substring(0, limit)}...'; diff --git a/lib/presentation/screens/staff/Enquiry/raise_enq_form.dart b/lib/presentation/screens/staff/Enquiry/raise_enq_form.dart index dd80d13..9193b18 100644 --- a/lib/presentation/screens/staff/Enquiry/raise_enq_form.dart +++ b/lib/presentation/screens/staff/Enquiry/raise_enq_form.dart @@ -15,7 +15,9 @@ 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'; +import '../../../themes/indicators/text_field_theme.dart'; +import '../../../themes/indicators/text_field_theme_inline_editor.dart' + hide UpperCaseTextFormatter; class RaiseEnqForm extends ConsumerStatefulWidget { final Function(Map data) onSubmit; @@ -384,7 +386,7 @@ class RaiseEnqFormState extends ConsumerState { // color: Color(0xFFFCFCFC), // margin: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 25), - height: MediaQuery.of(context).size.height * 0.16, + // height: MediaQuery.of(context).size.height * 0.16, decoration: BoxDecoration( color: Colors.white, // color: Colors.amber, @@ -405,27 +407,30 @@ class RaiseEnqFormState extends ConsumerState { child: FocusTraversalGroup( child: Form( key: _formKey, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - buildAgentName(context, fromHeader: true), - if (roleId != 'staff') ...[buildSelectStaffMem(context)], + child: Container( + // color: Colors.amber.shade100, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + buildAgentName(context, fromHeader: true), + if (roleId != 'staff') ...[buildSelectStaffMem(context)], - buildBroker(context), - buildInsurer(context, fromHeader: true), + buildBroker(context), + buildInsurer(context, fromHeader: true), - // buildInsuredName(context, fromHeader: true), - buildVehicleNumber(context), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - buildRefresh(context), - SizedBox(width: 5), - buildSave(context), - ], - ), - ], + // buildInsuredName(context, fromHeader: true), + buildVehicleNumber(context), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + buildRefresh(context), + SizedBox(width: 5), + buildSave(context), + ], + ), + ], + ), ), ), ), @@ -805,22 +810,31 @@ class RaiseEnqFormState extends ConsumerState { } Widget buildVehicleNumber(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Vehicle Number *', style: _subLabelTimeStyle), + return SizedBox( + width: MediaQuery.of(context).size.width * 0.15, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Vehicle Number *', style: _subLabelTimeStyle), - SizedBox(height: 5), - SizedBox( - width: MediaQuery.of(context).size.width * 0.15, - child: ThemedFormInlineField( + SizedBox(height: 5), + ThemedFormField( controller: controllers['regNo']!, - - hintText: 'Vehicle Number', borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), - padVertical: 13, + hintText: 'Vehicle Number', + txtwidth: MediaQuery.of(context).size.width * 0.26, + verticalPad: 15, isdense: true, + validator: (value) { + // Show backend validation message + if (fieldErrors['regNo'] != null && + fieldErrors['regNo']!.isNotEmpty) { + return fieldErrors['regNo']; // show API message + } + + return Validators.requiredField(value, "regNo"); + }, inputFormatters: [ UpperCaseTextFormatter(), // 👈 custom formatter for uppercase FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')), @@ -846,20 +860,54 @@ class RaiseEnqFormState extends ConsumerState { }); } }, - validator: (value) { - // Show backend validation message - if (fieldErrors['regNo'] != null && - fieldErrors['regNo']!.isNotEmpty) { - return fieldErrors['regNo']; // show API message - } - - return Validators.requiredField(value, "regNo"); - }, - - widthNone: true, ), - ), - ], + // ThemedFormInlineField( + // controller: controllers['regNo']!, + // + // hintText: 'Vehicle Number', + // borderColor: Color(0xFFE2E8F0), + // highlightColor: Color(0xFF50A398), + // padVertical: 13, + // isdense: true, + // inputFormatters: [ + // UpperCaseTextFormatter(), // 👈 custom formatter for uppercase + // FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')), + // ], + // // validator: (value) => Validators.requiredField(value, "regNo"), + // onChanged: (val) async { + // if (_isResetting) { + // print("Skipping API because _isResetting = true"); + // return; + // } + // print('Vehicle - $val'); + // final res = await apiService.CheckDuplicate(context, val); + // print('Vehicle res- $res'); + // + // if (res["message"] != null && res["message"] != "") { + // ToastHelper.showErrorToast(context, res['message']); + // setState(() { + // fieldErrors['regNo'] = res['message']; // store API message + // }); // 🔥 store backend message + // } else { + // setState(() { + // fieldErrors['regNo'] = res['message']; // store API message + // }); + // } + // }, + // validator: (value) { + // // Show backend validation message + // if (fieldErrors['regNo'] != null && + // fieldErrors['regNo']!.isNotEmpty) { + // return fieldErrors['regNo']; // show API message + // } + // + // return Validators.requiredField(value, "regNo"); + // }, + // // txtheight: 30, + // // txtwidth: MediaQuery.of(context).size.width * 0.01, + // ), + ], + ), ); } diff --git a/lib/presentation/themes/charts/barChart.dart b/lib/presentation/themes/charts/barChart.dart index 77f7dc9..1fcb895 100644 --- a/lib/presentation/themes/charts/barChart.dart +++ b/lib/presentation/themes/charts/barChart.dart @@ -49,12 +49,13 @@ class _CustomBarChartState extends State { return BarChartGroupData( x: x, groupVertically: false, - - // barsSpace: 20, + // showingTooltipIndicators: [0], + // barsSpace: 30, barRods: [ - BarChartRodData(toY: value1, color: Colors.cyan.shade300, width: 6), + BarChartRodData(toY: value1, color: Colors.cyan.shade300, width: 4), + // BarChartRodData(toY: 2, color: Colors.orange.shade300, width: 6), - BarChartRodData(toY: value2, color: Colors.orange.shade300, width: 6), + BarChartRodData(toY: value2, color: Colors.orange.shade300, width: 4), ], showingTooltipIndicators: touchedGroupIndex == x ? [0] : [], ); @@ -67,7 +68,72 @@ class _CustomBarChartState extends State { child: BarChart( BarChartData( alignment: BarChartAlignment.spaceBetween, + // verticalAxis: Axis.horizontal, rotationQuarterTurns: rotationTurns, + + // barTouchData: BarTouchData( + // // 1. Disable built-in touch handling + // handleBuiltInTouches: false, + // touchTooltipData: BarTouchTooltipData( + // // 3. Define the tooltip content + // getTooltipItem: (group, groupIndex, rod, rodIndex) { + // // You can customize the content here + // return BarTooltipItem( + // '${rod.toY.round().toString()}', // Explicit value + // GoogleFonts.poppins( + // color: Colors.black, + // fontSize: 8, + // fontWeight: FontWeight.w500, + // ), + // ); + // }, + // // Customize appearance + // getTooltipColor: (BarChartGroupData group) => Colors.transparent, + // tooltipRoundedRadius: 8, + // ), + // ), + barTouchData: BarTouchData( + enabled: true, + handleBuiltInTouches: true, + touchTooltipData: BarTouchTooltipData( + tooltipMargin: 6, + tooltipRoundedRadius: 6, + tooltipPadding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + // ask fl_chart to fit tooltip inside available space + fitInsideVertically: true, // if your fl_chart version supports it + fitInsideHorizontally: + true, // if your fl_chart version supports it + getTooltipItem: + ( + BarChartGroupData group, + int groupIndex, + BarChartRodData rod, + int rodIndex, + ) { + final textColor = rodIndex == 0 + ? Colors.cyan.shade300 + : Colors.orange.shade300; + final textStyle = TextStyle( + color: textColor, + fontSize: 12, + fontWeight: FontWeight.w600, + ); + return BarTooltipItem('${rod.toY.round()}', textStyle); + }, + ), + touchCallback: (FlTouchEvent e, BarTouchResponse? r) { + // optional: change state to highlight tapped group + if (r == null || r.spot == null) { + setState(() => touchedGroupIndex = -1); + return; + } + setState(() => touchedGroupIndex = r.spot!.touchedBarGroupIndex); + }, + ), + borderData: FlBorderData( show: true, border: Border( @@ -80,10 +146,14 @@ class _CustomBarChartState extends State { ), gridData: FlGridData( show: true, + // drawVerticalLine: true, drawVerticalLine: false, getDrawingHorizontalLine: (value) => FlLine( - color: AppColors.borderColor.withValues(alpha: 0.2), - strokeWidth: 1, + // color: AppColors.gridLinesColor.withValues(alpha: 0.2), + color: Colors.blueGrey.shade100, + // color: Colors.black, + // color: AppColors.borderColor, + strokeWidth: 0.3, ), ), titlesData: FlTitlesData( @@ -112,8 +182,30 @@ class _CustomBarChartState extends State { }, ), ), + + rightTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + return Transform.rotate( + angle: -3, + child: Padding( + padding: const EdgeInsets.only(right: 3.0), + child: Text( + value.toInt().toString(), + textAlign: TextAlign.end, + style: GoogleFonts.inter( + fontSize: 9, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + }, + reservedSize: 45, + ), + ), leftTitles: const AxisTitles(), - // rightTitles: const AxisTitles(), topTitles: const AxisTitles(), ), barGroups: widget.dataList.asMap().entries.map((e) { @@ -134,49 +226,8 @@ class _CustomBarChartState extends State { ? widget.dataList! .map((e) => e.value1 > e.value2 ? e.value1 : e.value2) .reduce((a, b) => a > b ? a : b) + - 5 + 15 : 20, - - // barTouchData: BarTouchData( - // enabled: true, - // handleBuiltInTouches: false, - // touchTooltipData: BarTouchTooltipData( - // getTooltipColor: (group) => Colors.transparent, - // tooltipMargin: 0, - // getTooltipItem: - // ( - // BarChartGroupData group, - // int groupIndex, - // BarChartRodData rod, - // int rodIndex, - // ) { - // return BarTooltipItem( - // rod.toY.toString(), - // TextStyle( - // fontWeight: FontWeight.bold, - // color: rod.color, - // fontSize: 18, - // shadows: const [ - // Shadow(color: Colors.black26, blurRadius: 12), - // ], - // ), - // ); - // }, - // ), - // touchCallback: (event, response) { - // if (event.isInterestedForInteractions && - // response != null && - // response.spot != null) { - // setState(() { - // touchedGroupIndex = response.spot!.touchedBarGroupIndex; - // }); - // } else { - // setState(() { - // touchedGroupIndex = -1; - // }); - // } - // }, - // ), ), ), ); diff --git a/lib/presentation/themes/charts/stackedBarChart.dart b/lib/presentation/themes/charts/stackedBarChart.dart new file mode 100644 index 0000000..c350051 --- /dev/null +++ b/lib/presentation/themes/charts/stackedBarChart.dart @@ -0,0 +1,165 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class CustomStackedBarChart extends StatefulWidget { + final List labels; + final List>> + data; // data[group][stackIndex] = [start, end, color] + final double maxY; + final int initialRotation; + + const CustomStackedBarChart({ + super.key, + required this.labels, + required this.data, + this.maxY = 100.0, + this.initialRotation = 1, + }); + + @override + State createState() => _CustomStackedBarChartState(); +} + +class _CustomStackedBarChartState extends State { + late int rotationTurns; + + @override + void initState() { + super.initState(); + rotationTurns = widget.initialRotation; + } + + double _toDouble(dynamic v) { + if (v == null) return 0.0; + if (v is double) return v; + if (v is int) return v.toDouble(); + return double.tryParse(v.toString()) ?? 0.0; + } + + BarChartGroupData _generateGroup(int xIndex) { + // Each group is a single stacked rod (one staff) + final stacks = widget.data[xIndex]; + final rodStackItems = []; + + for (var s in stacks) { + // s expected: [start, end, color] + final start = _toDouble(s[0]); + final end = _toDouble(s[1]); + final color = (s.length > 2 && s[2] is Color) + ? s[2] as Color + : Colors.grey; + rodStackItems.add(BarChartRodStackItem(start, end, color)); + } + + final maxEnd = rodStackItems.isNotEmpty + ? rodStackItems.map((r) => r.toY).reduce((a, b) => a > b ? a : b) + : 0.0; + + return BarChartGroupData( + x: xIndex, + groupVertically: false, + barRods: [ + BarChartRodData( + toY: maxEnd, + width: 12, + borderRadius: BorderRadius.zero, + rodStackItems: rodStackItems, + ), + BarChartRodData( + toY: maxEnd, + width: 12, + borderRadius: BorderRadius.zero, + rodStackItems: rodStackItems, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + final maxY = widget.maxY; + return AspectRatio( + aspectRatio: 1.6, + child: BarChart( + BarChartData( + alignment: BarChartAlignment.center, + rotationQuarterTurns: rotationTurns, + gridData: FlGridData( + show: true, + checkToShowHorizontalLine: (value) => value % (maxY / 5) == 0, + getDrawingHorizontalLine: (value) => + FlLine(color: Colors.grey.withOpacity(0.12), strokeWidth: 0.8), + drawVerticalLine: false, + ), + borderData: FlBorderData(show: false), + barTouchData: BarTouchData( + enabled: true, + touchTooltipData: BarTouchTooltipData( + tooltipRoundedRadius: 6, + tooltipPadding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 6, + ), + fitInsideHorizontally: true, + fitInsideVertically: true, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + return BarTooltipItem( + '${rod.toY.toStringAsFixed(2)}', + GoogleFonts.poppins( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ), + titlesData: FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 56, + getTitlesWidget: (value, meta) { + final i = value.toInt(); + if (i < 0 || i >= widget.labels.length) + return const SizedBox(); + return SideTitleWidget( + meta: meta, + child: Text( + widget.labels[i], + style: GoogleFonts.poppins( + fontSize: 10, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ); + }, + ), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: (value, meta) { + return Text( + value.toInt().toString(), + style: GoogleFonts.inter(fontSize: 9), + ); + }, + ), + ), + leftTitles: const AxisTitles(), + topTitles: const AxisTitles(), + ), + barGroups: List.generate( + widget.data.length, + (i) => _generateGroup(i), + ), + maxY: maxY, + ), + ), + ); + } +} diff --git a/lib/presentation/themes/indicators/text_field_theme.dart b/lib/presentation/themes/indicators/text_field_theme.dart index 97947a0..106e4c2 100644 --- a/lib/presentation/themes/indicators/text_field_theme.dart +++ b/lib/presentation/themes/indicators/text_field_theme.dart @@ -202,6 +202,7 @@ class ThemedFormField extends HookWidget { fontWeight: FontWeight.w400, // optional color: textColr ?? Colors.black, // optional ), + onChanged: onChanged, ), ), );