From c121c127dfd731ac8a167eb2d50926a854381b89 Mon Sep 17 00:00:00 2001 From: SurendarSuri30 Date: Sat, 4 Apr 2026 10:04:15 +0530 Subject: [PATCH] payout reports and payout upload --- .flutter-plugins-dependencies | 2 +- lib/core/routing/app_router.dart | 10 + lib/core/routing/routes.dart | 3 + lib/core/services/api_service.dart | 68 +- lib/presentation/layouts/appheader.dart | 43 + .../screens/payout/payout_details.dart | 2 +- .../screens/payout/payout_report.dart | 1214 +++++++++++++++++ .../screens/payout/payout_upload.dart | 720 ++++++++++ lib/presentation/widgets/drawer_menu.dart | 33 + 9 files changed, 2087 insertions(+), 8 deletions(-) create mode 100644 lib/presentation/screens/payout/payout_report.dart create mode 100644 lib/presentation/screens/payout/payout_upload.dart diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index c0032e0..2dc2f6f 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":"device_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","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":"pdf_render","path":"/home/venba/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","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":"syncfusion_flutter_pdfviewer","path":"/home/venba/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-31.1.19/","native_build":true,"dependencies":["device_info_plus"],"dev_dependency":false},{"name":"url_launcher_ios","path":"/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":"device_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","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":"pdf_render","path":"/home/venba/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","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":"syncfusion_flutter_pdfviewer","path":"/home/venba/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-31.1.19/","native_build":true,"dependencies":["device_info_plus"],"dev_dependency":false},{"name":"url_launcher_android","path":"/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":"device_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","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":"pdf_render","path":"/home/venba/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","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":"syncfusion_pdfviewer_macos","path":"/home/venba/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_macos-31.1.19/","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":"device_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","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":"syncfusion_pdfviewer_linux","path":"/home/venba/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_linux-31.1.19/","native_build":true,"dependencies":[],"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":"device_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","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":"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":"syncfusion_pdfviewer_windows","path":"/home/venba/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_windows-31.1.19/","native_build":true,"dependencies":[],"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":"device_info_plus","path":"/home/venba/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","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":"pdf_render","path":"/home/venba/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","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":"syncfusion_pdfviewer_web","path":"/home/venba/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_web-31.1.19/","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":"device_info_plus","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"firebase_auth","dependencies":["firebase_auth_web","firebase_core"]},{"name":"firebase_auth_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_messaging","dependencies":["firebase_core","firebase_messaging_web"]},{"name":"firebase_messaging_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"fluttertoast","dependencies":[]},{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"local_auth","dependencies":["local_auth_android","local_auth_darwin","local_auth_windows"]},{"name":"local_auth_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"local_auth_darwin","dependencies":[]},{"name":"local_auth_windows","dependencies":[]},{"name":"open_file","dependencies":["open_file_android","open_file_web","open_file_ios","open_file_mac","open_file_windows","open_file_linux"]},{"name":"open_file_android","dependencies":[]},{"name":"open_file_ios","dependencies":[]},{"name":"open_file_linux","dependencies":[]},{"name":"open_file_mac","dependencies":[]},{"name":"open_file_web","dependencies":[]},{"name":"open_file_windows","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"pdf_render","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"syncfusion_flutter_pdfviewer","dependencies":["url_launcher","device_info_plus","syncfusion_pdfviewer_web","syncfusion_pdfviewer_macos","syncfusion_pdfviewer_windows","syncfusion_pdfviewer_linux"]},{"name":"syncfusion_pdfviewer_linux","dependencies":[]},{"name":"syncfusion_pdfviewer_macos","dependencies":[]},{"name":"syncfusion_pdfviewer_web","dependencies":[]},{"name":"syncfusion_pdfviewer_windows","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2026-03-31 18:32:27.151243","version":"3.41.4","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.2.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_darwin-1.6.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_ios-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_flutter_pdfviewer","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-31.1.19/","native_build":true,"dependencies":["device_info_plus"],"dev_dependency":false},{"name":"url_launcher_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.4/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.31/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_android-7.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_android-1.0.52/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"open_file_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_android-1.0.6/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_android-2.2.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.13/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_flutter_pdfviewer","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-31.1.19/","native_build":true,"dependencies":["device_info_plus"],"dev_dependency":false},{"name":"url_launcher_android","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"firebase_messaging","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging-16.0.4/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"flutter_secure_storage_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"google_sign_in_ios","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_ios-6.2.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_darwin","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_darwin-1.6.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_mac","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_mac-1.0.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"syncfusion_pdfviewer_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_macos-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.3/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_linux-0.0.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"syncfusion_pdfviewer_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_linux-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_linux","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2/","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"firebase_auth","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth-6.1.2/","native_build":true,"dependencies":["firebase_core"],"dev_dependency":false},{"name":"firebase_core","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core-4.2.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_secure_storage_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"local_auth_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/local_auth_windows-1.0.11/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_file_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_windows-0.0.3/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"syncfusion_pdfviewer_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_windows-31.1.19/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_windows","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5/","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"connectivity_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/connectivity_plus-6.1.5/","dependencies":[],"dev_dependency":false},{"name":"device_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/device_info_plus-11.5.0/","dependencies":[],"dev_dependency":false},{"name":"file_picker","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/file_picker-10.3.7/","dependencies":[],"dev_dependency":false},{"name":"firebase_auth_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_auth_web-6.1.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"firebase_core_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_core_web-3.3.0/","dependencies":[],"dev_dependency":false},{"name":"firebase_messaging_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/firebase_messaging_web-4.1.0/","dependencies":["firebase_core_web"],"dev_dependency":false},{"name":"flutter_secure_storage_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/fluttertoast-9.0.0/","dependencies":[],"dev_dependency":false},{"name":"google_sign_in_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/google_sign_in_web-1.1.0/","dependencies":[],"dev_dependency":false},{"name":"open_file_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/open_file_web-0.0.4/","dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/package_info_plus-8.3.1/","dependencies":[],"dev_dependency":false},{"name":"pdf_render","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/pdf_render-1.4.12/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false},{"name":"syncfusion_pdfviewer_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_web-31.1.19/","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"/Users/venbateam/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":[]},{"name":"device_info_plus","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"firebase_auth","dependencies":["firebase_auth_web","firebase_core"]},{"name":"firebase_auth_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_messaging","dependencies":["firebase_core","firebase_messaging_web"]},{"name":"firebase_messaging_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"fluttertoast","dependencies":[]},{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"local_auth","dependencies":["local_auth_android","local_auth_darwin","local_auth_windows"]},{"name":"local_auth_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"local_auth_darwin","dependencies":[]},{"name":"local_auth_windows","dependencies":[]},{"name":"open_file","dependencies":["open_file_android","open_file_web","open_file_ios","open_file_mac","open_file_windows","open_file_linux"]},{"name":"open_file_android","dependencies":[]},{"name":"open_file_ios","dependencies":[]},{"name":"open_file_linux","dependencies":[]},{"name":"open_file_mac","dependencies":[]},{"name":"open_file_web","dependencies":[]},{"name":"open_file_windows","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"pdf_render","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"syncfusion_flutter_pdfviewer","dependencies":["url_launcher","device_info_plus","syncfusion_pdfviewer_web","syncfusion_pdfviewer_macos","syncfusion_pdfviewer_windows","syncfusion_pdfviewer_linux"]},{"name":"syncfusion_pdfviewer_linux","dependencies":[]},{"name":"syncfusion_pdfviewer_macos","dependencies":[]},{"name":"syncfusion_pdfviewer_web","dependencies":[]},{"name":"syncfusion_pdfviewer_windows","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2026-04-03 15:06:40.682541","version":"3.35.7","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 91e3731..25e43ee 100644 --- a/lib/core/routing/app_router.dart +++ b/lib/core/routing/app_router.dart @@ -37,6 +37,8 @@ import '../../presentation/screens/login/login_screen.dart'; import '../../presentation/screens/payout/payout_screen.dart'; import '../../presentation/screens/payout/payout_details.dart'; import '../../presentation/screens/payout/payout_list.dart'; +import '../../presentation/screens/payout/payout_upload.dart'; +import '../../presentation/screens/payout/payout_report.dart'; import '../../presentation/screens/splash/splash_screen.dart'; import '../../presentation/screens/staff/Enquiry/enquiry_inline_Edit_17Nov.dart'; import '../../presentation/screens/staff/Enquiry/enquiry_inline_list.dart'; @@ -102,6 +104,14 @@ final GoRouter appRouter = GoRouter( return PayOutDetails(editItem: editItem); }, ), + GoRoute( + path: AppRoutes.payoutUpload, + builder: (context, state) => const PayoutUploadScreen(), + ), + GoRoute( + path: AppRoutes.payoutReport, + builder: (context, state) => const PayoutReportScreen(), + ), GoRoute( path: AppRoutes.profile, diff --git a/lib/core/routing/routes.dart b/lib/core/routing/routes.dart index f140743..8b04c83 100644 --- a/lib/core/routing/routes.dart +++ b/lib/core/routing/routes.dart @@ -52,6 +52,9 @@ class AppRoutes { static const String vehicleTypeLst = '/vehicleTypeLst'; static const String payoutList = '/payoutList'; static const String payoutDetails = '/payoutDetails'; + static const String payoutUpload = '/payoutUpload'; + /// Accounts + agent: commission payout report (list API TBD). + static const String payoutReport = '/payoutReport'; static const String payoutGrid = '/payoutGrid'; static const String gridList = '/gridList'; static const String gridUpload = '/gridUpload'; diff --git a/lib/core/services/api_service.dart b/lib/core/services/api_service.dart index 96b2252..74cc0a6 100644 --- a/lib/core/services/api_service.dart +++ b/lib/core/services/api_service.dart @@ -2050,7 +2050,7 @@ class ApiService { // --------------------------------- PayOut Module---------------------------------------------- Future> getCommissionRateList(data) async { print("getCommissionRateList------- $data}"); - // final url = Uri.parse('${Env.apiUrl}/invoice/commission-rate-list'); + final url = Uri.parse('${Env.apiUrl}/invoice/commission-rate-list'); // final url = Uri.parse('http://localhost/nhance_partner_be/invoice/commission-rate-list'); print("getCommissionRateList 1"); // final token = await getToken(); // Fetch token @@ -2350,7 +2350,6 @@ class ApiService { } Future> uploadPolicyCommissionExcel({ - required dynamic id, required PlatformFile file, bool proceedPartnerMismatch = false, }) async { @@ -2370,12 +2369,11 @@ class ApiService { request.files.add( http.MultipartFile.fromBytes( - 'file', + 'commission_excel', file.bytes!, filename: file.name, ), ); - request.fields['id'] = id.toString(); if (proceedPartnerMismatch) { request.fields['proceed_partner_mismatch'] = '1'; } @@ -2390,9 +2388,21 @@ class ApiService { } if (streamedResponse.statusCode != 200) { + try { + final decoded = jsonDecode(responseBody); + if (decoded is Map) { + return { + ...decoded, + if (!decoded.containsKey('code')) 'code': streamedResponse.statusCode, + }; + } + } catch (_) {} return { - 'status': 'error', - 'message': 'Server Error: ${streamedResponse.statusCode}', + 'status': 'failed', + 'code': streamedResponse.statusCode, + 'message': responseBody.isNotEmpty + ? responseBody + : 'Server Error: ${streamedResponse.statusCode}', }; } @@ -2613,6 +2623,52 @@ class ApiService { ); } + /// Static sample Excel for commission upload (template). + Future downloadCommissionUploadSampleExcel() async { + await getPdfDownload( + 'policy/downloadCommissionUploadSampleExcel', + 'commission_upload_sample', + ); + } + + /// GET `policy/commissionPayoutReport` — optional query: `from_date`, `to_date` (yyyy-MM-dd), `agent_id`, `payout_raised` (`all` / `yes` / `no`). + Future> fetchPayoutReportList({ + String? fromDate, + String? toDate, + String? agentId, + String? payoutRaised, + }) async { + if (_token == null) { + await _initializeToken(); + } + + final query = {}; + if (fromDate != null && fromDate.isNotEmpty) { + query['from_date'] = fromDate; + } + if (toDate != null && toDate.isNotEmpty) { + query['to_date'] = toDate; + } + if (agentId != null && agentId.trim().isNotEmpty) { + query['agent_id'] = agentId.trim(); + } + if (payoutRaised != null && payoutRaised.isNotEmpty) { + query['payout_raised'] = payoutRaised; + } + + final url = Uri.parse('${Env.apiUrl}policy/commissionPayoutReport').replace( + queryParameters: query.isEmpty ? null : query, + ); + + final headers = { + 'Authorization': 'Bearer $_token', + 'app-signature': Env.App_Signature, + }; + + final response = await _makeGetRequest(url, headers); + return response; + } + Future generatePolicyExcel(managerId, fromDate, toDate, searchValue,flag,insurer) async { final url = Uri.parse( '${Env.apiUrl}reports/policy-excel?manager_id=$managerId&from_date=${fromDate ?? ''}&to_date=${toDate ?? ''}&search=${searchValue ?? ''}&show_policy_report=${flag ?? ''}&insurer_id=${insurer ?? ''}', diff --git a/lib/presentation/layouts/appheader.dart b/lib/presentation/layouts/appheader.dart index 485c14d..595869b 100644 --- a/lib/presentation/layouts/appheader.dart +++ b/lib/presentation/layouts/appheader.dart @@ -105,6 +105,12 @@ class _AppHeaderState extends ConsumerState { if (route.contains('payoutgrid')) { return 'PayoutGrid'; } + if (route.contains('payoutupload')) { + return 'PayoutUpload'; + } + if (route.contains('payoutreport')) { + return 'PayoutReport'; + } if (route.contains('invoice') || (route.contains('payout') && !route.contains('payoutgrid'))) { return 'Payout'; @@ -509,6 +515,43 @@ class _AppHeaderState extends ConsumerState { context.go(AppRoutes.payoutList); }, ), + const SizedBox(width: 8), + _buildMenuItem( + icon: Icons.upload_file_outlined, + label: "Payout Upload", + isActive: activeMenu == 'PayoutUpload', + onTap: () { + _hidePopup(); + setState(() => _activeMenu = 'PayoutUpload'); + context.go(AppRoutes.payoutUpload); + }, + ), + const SizedBox(width: 8), + _buildMenuItem( + icon: Icons.table_chart_outlined, + label: "Payout Report", + isActive: activeMenu == 'PayoutReport', + onTap: () { + _hidePopup(); + setState(() => _activeMenu = 'PayoutReport'); + context.go(AppRoutes.payoutReport); + }, + ), + ], + + // Partner (agent): Payout Report — same route as Accounts + if (role == 'agent') ...[ + const SizedBox(width: 8), + _buildMenuItem( + icon: Icons.table_chart_outlined, + label: "Payout Report", + isActive: activeMenu == 'PayoutReport', + onTap: () { + _hidePopup(); + setState(() => _activeMenu = 'PayoutReport'); + context.go(AppRoutes.payoutReport); + }, + ), ], const Spacer(), diff --git a/lib/presentation/screens/payout/payout_details.dart b/lib/presentation/screens/payout/payout_details.dart index d5480bf..0067cf0 100644 --- a/lib/presentation/screens/payout/payout_details.dart +++ b/lib/presentation/screens/payout/payout_details.dart @@ -1028,7 +1028,7 @@ class _PayOutDetailsState extends ConsumerState { } final response = await apiService.uploadPolicyCommissionExcel( - id: selectedAgentId!.first, + // id: selectedAgentId!.first, file: selectedFile, proceedPartnerMismatch: partnerMismatchCount > 0, ); diff --git a/lib/presentation/screens/payout/payout_report.dart b/lib/presentation/screens/payout/payout_report.dart new file mode 100644 index 0000000..fa3dba4 --- /dev/null +++ b/lib/presentation/screens/payout/payout_report.dart @@ -0,0 +1,1214 @@ +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; + +import '../../../core/services/api_service.dart'; +import '../../../data/utils/Pagination.dart'; +import '../../../data/utils/toastNotification.dart'; +import '../../layouts/main_layout.dart'; +import '../../layouts/responsive_layout.dart'; +import '../../providers/manager_provider.dart'; +import '../../providers/userRoleProvider.dart'; +import '../../themes/indicators/date_field_theme.dart'; +import '../../themes/indicators/search_field_theme.dart'; + +/// Payout report — layout and styling aligned with [policylist] policy list screen. +class PayoutReportScreen extends ConsumerStatefulWidget { + const PayoutReportScreen({super.key}); + + @override + ConsumerState createState() => _PayoutReportScreenState(); +} + +class _PayoutReportScreenState extends ConsumerState { + late ApiService apiService; + int currentPage = 1; + int itemsPerPage = 10; + bool isLoading = false; + + final TextEditingController _startController = TextEditingController(); + final TextEditingController _endController = TextEditingController(); + final TextEditingController _searchController = TextEditingController(); + + List> _rows = []; + List> _filtered = []; + List> _agentOptions = []; + String? _selectedAgentId; + /// `null` = "Select Payout Raised" (not chosen). `'all'` | `'yes'` | `'no'` sent to API. + String? _payoutRaised; + + bool _hasSearched = false; + + /// From API root `count` and `summary.{total,raised,pending}`. + int? _reportCount; + num? _summaryTotal; + num? _summaryRaised; + num? _summaryPending; + + int? _managerId; + + bool _isAgentRole() { + final r = (ref.read(userRoleProvider) ?? '').toString().trim().toLowerCase(); + return r == 'agent' || r == 'a'; + } + + /// `received_or_not` equals `no` (case-insensitive). + bool _receivedIsNo(Map item) { + final v = (item['received_or_not'] ?? '').toString().trim().toLowerCase(); + return v == 'no'; + } + + String? _agentIdForApi() { + if (_isAgentRole()) { + final uid = ref.read(userIdProvider); + return uid?.toString(); + } + return _selectedAgentId; + } + + static const _tableHeaderBg = Color(0xFFF1F5F9); + static const _rowBorder = Color(0xFFEAEAEA); + /// Pending summary chip + `received_or_not == no` row highlight (same palette). + static const _pendingChipBg = Color(0xFFFFEBEE); + static const _pendingChipBorder = Color(0xFFEF9A9A); + static const _pendingChipValue = Color(0xFFC62828); + static const _filterStripMobileBg = Color(0xffD9EBE8); + /// Closed filter fields (light outline, white fill) — first design. + static const _filterFieldBorder = Color(0xFFE2E8F0); + static const _filterFocusTeal = Color(0xFF50A398); + + static final _dataBold = GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w400, + color: const Color(0xFF000000), + ); + + /// Table column titles (policy list: bold header row). + static final _headerColumn = GoogleFonts.poppins( + fontSize: 11.2, + fontWeight: FontWeight.w600, + color: const Color(0xFF1E293B), + ); + + InputDecoration _filterDropdownDecoration({required String hintText}) { + return InputDecoration( + isDense: true, + hintText: hintText, + hintStyle: GoogleFonts.inter(fontSize: 11, color: const Color(0xFF94A3B8)), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFieldBorder, width: 1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFieldBorder, width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFocusTeal, width: 1.5), + ), + ); + } + + /// `agent/agentList` exposes display name as `name`; older payloads may use `agent_name`. + String _partnerDisplayName(Map m) { + final v = m['name'] ?? m['agent_name'] ?? m['partner_name'] ?? ''; + return v.toString().trim(); + } + + Map _allAgentItem() => { + 'id': '', + 'name': 'Select Agent', + 'agent_name': 'Select Agent', + 'agent_code': '', + }; + + Map _selectedAgentMap() { + if (_selectedAgentId == null || _selectedAgentId!.isEmpty) { + return _allAgentItem(); + } + try { + final m = _agentOptions.firstWhere( + (a) => a['id'].toString() == _selectedAgentId, + ); + return Map.from(m); + } catch (_) { + return _allAgentItem(); + } + } + + List> _payoutRaisedChoices() => [ + {'v': null, 'label': 'Select Payout Raised'}, + {'v': 'all', 'label': 'All'}, + {'v': 'yes', 'label': 'Yes'}, + {'v': 'no', 'label': 'No'}, + ]; + + Map _selectedPayoutRaisedMap() { + for (final e in _payoutRaisedChoices()) { + if (e['v'] == _payoutRaised) return e; + } + return _payoutRaisedChoices().first; + } + + Widget _buildAgentDropdownSearch(double width) { + final agents = _agentOptions + .where((a) => (a['id']?.toString() ?? '').isNotEmpty) + .map((a) => Map.from(a)) + .toList(); + + return SizedBox( + height: 35, + width: width, + child: DropdownSearch>( + selectedItem: _selectedAgentMap(), + items: (filter, infiniteScrollProps) { + return [_allAgentItem(), ...agents]; + }, + itemAsString: (m) { + if ((m['id']?.toString() ?? '').isEmpty) { + return 'Select Agent'; + } + final name = _partnerDisplayName(m); + final code = (m['agent_code'] ?? '').toString(); + return '$name $code'.trim(); + }, + compareFn: (a, b) => + (a['id']?.toString() ?? '') == (b['id']?.toString() ?? ''), + decoratorProps: DropDownDecoratorProps( + decoration: _filterDropdownDecoration(hintText: 'Select Agent'), + ), + dropdownBuilder: (context, selectedItem) { + final m = selectedItem ?? _allAgentItem(); + final isAll = (m['id']?.toString() ?? '').isEmpty; + final name = _partnerDisplayName(m); + final code = (m['agent_code'] ?? '').toString(); + final text = isAll + ? 'Select Agent' + : name.isEmpty + ? code + : (code.isNotEmpty ? '$name — $code' : name); + return Align( + alignment: Alignment.centerLeft, + child: Text( + text, + style: GoogleFonts.inter( + fontSize: 11, + color: isAll + ? const Color(0xFF94A3B8) + : const Color(0xFF0F172A), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ); + }, + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: const BoxConstraints(maxHeight: 280), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + borderRadius: BorderRadius.circular(8), + ), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: 'Select Agent', + hintStyle: + GoogleFonts.inter(fontSize: 12, color: const Color(0xFF94A3B8)), + isDense: true, + contentPadding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFieldBorder), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFieldBorder), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: + const BorderSide(color: _filterFocusTeal, width: 1.5), + ), + ), + ), + itemBuilder: (context, item, isDisabled, isSelected) { + final isAll = (item['id']?.toString() ?? '').isEmpty; + return Container( + color: isSelected ? const Color(0xFFF1F5F9) : null, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: isAll + ? Text( + 'Select Agent', + style: GoogleFonts.inter( + fontSize: 12, + color: const Color(0xFF64748B), + ), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + () { + final n = _partnerDisplayName(item); + final c = (item['agent_code'] ?? '').toString(); + return (n.isEmpty ? c : n).toUpperCase(); + }(), + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w700, + color: const Color(0xFF0F172A), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + (item['agent_code'] ?? '').toString(), + style: GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w400, + color: const Color(0xFF94A3B8), + ), + ), + ], + ), + ), + ); + }, + ), + onChanged: (val) { + setState(() { + final id = val?['id']?.toString() ?? ''; + _selectedAgentId = id.isEmpty ? null : id; + }); + }, + ), + ); + } + + Widget _buildPayoutRaisedDropdownSearch(double width) { + final choices = _payoutRaisedChoices(); + return SizedBox( + height: 35, + width: width, + child: DropdownSearch>( + selectedItem: _selectedPayoutRaisedMap(), + items: (filter, infiniteScrollProps) => choices, + itemAsString: (m) => (m['label'] ?? '').toString(), + compareFn: (a, b) => a['v'] == b['v'], + decoratorProps: DropDownDecoratorProps( + decoration: _filterDropdownDecoration(hintText: 'Select Payout Raised'), + ), + dropdownBuilder: (context, selectedItem) { + final m = selectedItem ?? _payoutRaisedChoices().first; + final label = (m['label'] ?? '').toString(); + final isUnset = m['v'] == null; + return Align( + alignment: Alignment.centerLeft, + child: Text( + label, + style: GoogleFonts.inter( + fontSize: 11, + color: isUnset + ? const Color(0xFF94A3B8) + : const Color(0xFF0F172A), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ); + }, + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: const BoxConstraints(maxHeight: 220), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + borderRadius: BorderRadius.circular(8), + ), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: 'Select Payout Raised', + hintStyle: + GoogleFonts.inter(fontSize: 12, color: const Color(0xFF94A3B8)), + isDense: true, + contentPadding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFieldBorder), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: _filterFieldBorder), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: + const BorderSide(color: _filterFocusTeal, width: 1.5), + ), + ), + ), + itemBuilder: (context, item, isDisabled, isSelected) { + final isUnset = item['v'] == null; + return Container( + color: isSelected ? const Color(0xFFF1F5F9) : null, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Text( + (item['label'] ?? '').toString(), + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: isUnset ? FontWeight.w400 : FontWeight.w500, + color: isUnset + ? const Color(0xFF64748B) + : const Color(0xFF0F172A), + ), + ), + ); + }, + ), + onChanged: (val) { + setState(() { + if (val == null) { + _payoutRaised = null; + } else { + _payoutRaised = val['v'] as String?; + } + }); + }, + ), + ); + } + + @override + void initState() { + super.initState(); + apiService = ApiService(); + Future.microtask(() async { + _managerId = ref.read(managerIdProvider); + if (_managerId != null && !_isAgentRole()) { + await _loadAgents(); + } + }); + } + + @override + void dispose() { + _startController.dispose(); + _endController.dispose(); + _searchController.dispose(); + super.dispose(); + } + + Future _loadAgents() async { + final mid = _managerId; + if (mid == null) return; + try { + final response = await apiService.fetchAgentUserList(mid); + if (response['status'] == 'success' && response['data'] is List) { + if (mounted) { + setState(() { + _agentOptions = List>.from(response['data']); + }); + } + } + } catch (_) {} + } + + String? _dateToIso(String ddMmYyyy) { + final t = ddMmYyyy.trim(); + if (t.isEmpty) return null; + try { + final d = DateFormat('dd-MM-yyyy').parseStrict(t); + return DateFormat('yyyy-MM-dd').format(d); + } catch (_) { + return null; + } + } + + bool _validateFiltersForSearch() { + final from = _dateToIso(_startController.text); + final to = _dateToIso(_endController.text); + final startText = _startController.text.trim(); + final endText = _endController.text.trim(); + if (startText.isNotEmpty || endText.isNotEmpty) { + if (from == null || to == null) { + ToastHelper.showWarningToast( + context, + 'Enter both From date and To date, or clear both.', + ); + return false; + } + } + final hasDates = from != null && to != null; + final hasAgent = _isAgentRole() + ? ref.read(userIdProvider) != null + : (_selectedAgentId != null && _selectedAgentId!.trim().isNotEmpty); + final hasPayoutRaised = _payoutRaised != null; + if (!hasDates && !hasAgent && !hasPayoutRaised) { + ToastHelper.showWarningToast( + context, + _isAgentRole() + ? 'Select at least one filter: date range or payout raised.' + : 'Select at least one filter: date range, agent, or payout raised.', + ); + return false; + } + return true; + } + + Future _loadReport() async { + if (!_validateFiltersForSearch()) return; + + setState(() { + isLoading = true; + _hasSearched = true; + }); + try { + final response = await apiService.fetchPayoutReportList( + fromDate: _dateToIso(_startController.text), + toDate: _dateToIso(_endController.text), + agentId: _agentIdForApi(), + payoutRaised: _payoutRaised, + ); + if (!mounted) return; + + final data = response['data']; + final ok = response['status']?.toString().toLowerCase() == 'success' || + response['code'] == 200 || + data is List; + if (ok) { + List> next = []; + if (data is List) { + next = data.map((e) { + if (e is Map) return e; + if (e is Map) return Map.from(e); + return {}; + }).toList(); + } else if (data is Map) { + next = [Map.from(data)]; + } + + int? count; + final cr = response['count']; + if (cr is int) { + count = cr; + } else if (cr != null) { + count = int.tryParse(cr.toString()); + } + num? sumTotal; + num? sumRaised; + num? sumPending; + final sm = response['summary']; + if (sm is Map) { + final m = Map.from(sm); + sumTotal = _parseNum(m['total']); + sumRaised = _parseNum(m['raised']); + sumPending = _parseNum(m['pending']); + } + + setState(() { + _rows = next; + _reportCount = count; + _summaryTotal = sumTotal; + _summaryRaised = sumRaised; + _summaryPending = sumPending; + _applySearch(_searchController.text); + currentPage = 1; + }); + } else { + setState(() { + _rows = []; + _filtered = []; + _reportCount = null; + _summaryTotal = null; + _summaryRaised = null; + _summaryPending = null; + }); + final msg = response['message']?.toString(); + if (msg != null && msg.isNotEmpty) { + ToastHelper.showWarningToast(context, msg); + } + } + } catch (e) { + if (mounted) { + ToastHelper.showErrorToast(context, e.toString()); + } + } finally { + if (mounted) setState(() => isLoading = false); + } + } + + void _filterData(String query) { + setState(() { + _applySearch(query); + currentPage = 1; + }); + } + + void _applySearch(String query) { + final q = query.trim().toLowerCase(); + if (q.isEmpty) { + _filtered = List.from(_rows); + return; + } + _filtered = _rows.where((row) { + return row.values.any( + (v) => v != null && v.toString().toLowerCase().contains(q), + ); + }).toList(); + } + + List> get _paginated { + if (_filtered.isEmpty) return []; + final maxPage = (_filtered.length / itemsPerPage).ceil().clamp(1, 999999); + final safe = currentPage.clamp(1, maxPage); + final start = (safe - 1) * itemsPerPage; + final end = (start + itemsPerPage).clamp(0, _filtered.length); + return _filtered.sublist(start, end); + } + + String _cell(Map row, List keys) { + for (final k in keys) { + final v = row[k]; + if (v != null && v.toString().trim().isNotEmpty) { + return v.toString(); + } + } + return '-'; + } + + static num? _parseNum(dynamic v) { + if (v == null) return null; + if (v is num) return v; + return num.tryParse(v.toString()); + } + + String _fmtSummaryAmount(num? v) { + if (v == null) return '-'; + return NumberFormat('#,##0.00').format(v); + } + + Widget _buildSummaryChips() { + if (!_hasSearched || isLoading) return const SizedBox.shrink(); + // API `data: []` — no rows; hide summary even if count/summary are zeros. + if (_rows.isEmpty) return const SizedBox.shrink(); + final hasAny = _reportCount != null || + _summaryTotal != null || + _summaryRaised != null || + _summaryPending != null; + if (!hasAny) return const SizedBox.shrink(); + + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Wrap( + spacing: 10, + runSpacing: 8, + children: [ + _summaryChip( + label: 'Count', + value: _reportCount != null ? '$_reportCount' : '-', + bg: const Color(0xFFE8F5E9), + border: const Color(0xFFBBDCB5), + labelColor: const Color(0xFF64748B), + valueColor: const Color(0xFF2E7D32), + ), + _summaryChip( + label: 'Total', + value: _fmtSummaryAmount(_summaryTotal), + bg: const Color(0xFFECEFF1), + border: const Color(0xFFB0BEC5), + labelColor: const Color(0xFF64748B), + valueColor: const Color(0xFF37474F), + ), + _summaryChip( + label: 'Raised', + value: _fmtSummaryAmount(_summaryRaised), + bg: const Color(0xFFE0F2F1), + border: const Color(0xFF80CBC4), + labelColor: const Color(0xFF64748B), + valueColor: const Color(0xFF00695C), + ), + _summaryChip( + label: 'Pending', + value: _fmtSummaryAmount(_summaryPending), + bg: _pendingChipBg, + border: _pendingChipBorder, + labelColor: const Color(0xFF64748B), + valueColor: _pendingChipValue, + ), + ], + ), + ); + } + + Widget _summaryChip({ + required String label, + required String value, + required Color bg, + required Color border, + required Color labelColor, + required Color valueColor, + }) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: border, width: 1), + ), + child: RichText( + text: TextSpan( + style: GoogleFonts.inter(fontSize: 12, color: labelColor), + children: [ + TextSpan(text: '$label: '), + TextSpan( + text: value, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w700, + color: valueColor, + ), + ), + ], + ), + ), + ); + } + + void _onExport() { + if (!_hasSearched || _filtered.isEmpty) { + ToastHelper.showWarningToast( + context, + 'Load report data with Search before exporting.', + ); + return; + } + ToastHelper.showInfoToast( + context, + 'Export can be wired to the same filters as the report when needed.', + ); + } + + Widget _buildDateField({ + required TextEditingController controller, + required String hintText, + required bool isStart, + }) { + return SizedBox( + height: 35, + child: ThemedDateField( + hintText: hintText, + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.1, + controller: controller, + lastDate: isStart ? null : DateTime.now(), + onDateSelected: (date) { + controller.text = DateFormat('dd-MM-yyyy').format(date); + }, + ), + ); + } + + Widget _filterControlsRow(BuildContext context) { + final spacing = 5.0; + final isMobile = ResponsiveLayout.isMobile(context); + final role = (ref.watch(userRoleProvider) ?? '').toString().trim().toLowerCase(); + final showAgentFilter = role != 'agent' && role != 'a'; + + final fields = Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDateField( + controller: _startController, + hintText: 'Select Date', + isStart: true, + ), + SizedBox(width: spacing), + _buildDateField( + controller: _endController, + hintText: 'Select Date', + isStart: false, + ), + if (showAgentFilter) ...[ + SizedBox(width: spacing), + _buildAgentDropdownSearch( + isMobile ? 168.0 : MediaQuery.of(context).size.width * 0.13, + ), + ], + SizedBox(width: spacing), + _buildPayoutRaisedDropdownSearch( + isMobile ? 168.0 : MediaQuery.of(context).size.width * 0.13, + ), + SizedBox(width: spacing), + Tooltip( + message: 'Search', + child: IconButton( + icon: const Icon(Icons.search_rounded, size: 18, color: Color(0xFF94A3B8)), + onPressed: _loadReport, + ), + ), + Tooltip( + message: 'Refresh', + child: IconButton( + icon: const Icon(Icons.refresh, size: 18, color: Color(0xFF94A3B8)), + onPressed: () { + _startController.clear(); + _endController.clear(); + _searchController.clear(); + setState(() { + _selectedAgentId = null; + _payoutRaised = null; + _rows = []; + _filtered = []; + _hasSearched = false; + _reportCount = null; + _summaryTotal = null; + _summaryRaised = null; + _summaryPending = null; + currentPage = 1; + }); + }, + ), + ), + ], + ); + + return Container( + padding: const EdgeInsets.all(8.0), + color: isMobile ? _filterStripMobileBg : null, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: fields, + ), + ); + } + + Widget _buildSearchAndActions(BuildContext context) { + final isMobile = ResponsiveLayout.isMobile(context); + final searchField = ThemedSearchField( + hintText: 'Search', + onChanged: _filterData, + controller: _searchController, + backgroundColor: const Color(0xFFFFFFFF), + txtHeight: 30, + txtwidth: isMobile + ? MediaQuery.of(context).size.width * 0.7 + : MediaQuery.of(context).size.width * 0.15, + ); + final actions = [ + Tooltip( + message: 'Export Excel', + child: InkWell( + onTap: _onExport, + child: Container( + padding: const EdgeInsets.all(7.0), + decoration: BoxDecoration( + color: const Color(0xFF2E7D6E), + borderRadius: BorderRadius.circular(8.0), + ), + child: Image.asset( + 'assets/miscellaneous/export.png', + height: 13, + width: 13, + ), + ), + ), + ), + const SizedBox(width: 8), + ]; + // Desktop: no Spacer — this Row sits after Expanded(filter); Row+Spacer has + // unbounded intrinsic width and triggers RenderFlex layout failure. + if (!isMobile) { + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + searchField, + const SizedBox(width: 10), + ...actions, + ], + ); + } + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + searchField, + const Spacer(), + ...actions, + ], + ); + } + + Widget _buildFilterBar(BuildContext context) { + final isMobile = ResponsiveLayout.isMobile(context); + if (isMobile) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _filterControlsRow(context), + const SizedBox(height: 8), + _buildSearchAndActions(context), + ], + ); + } + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _filterControlsRow(context)), + _buildSearchAndActions(context), + ], + ); + } + + Widget _tableHeader() { + return Container( + decoration: BoxDecoration( + color: _tableHeaderBg, + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Row( + children: [ + Expanded(flex: 1, child: Text('S NO', style: _headerColumn)), + Expanded(flex: 2, child: Text('POLICY NO', style: _headerColumn)), + Expanded(flex: 2, child: Text('REG NO', style: _headerColumn)), + Expanded(flex: 1, child: Text('WEIGHT', style: _headerColumn)), + Expanded(flex: 2, child: Text('FUEL TYPE', style: _headerColumn)), + Expanded(flex: 2, child: Text('VEHICLE TYPE', style: _headerColumn)), + Expanded(flex: 2, child: Text('PREMIUM', style: _headerColumn)), + Expanded(flex: 2, child: Text('COMMISSION', style: _headerColumn)), + Expanded(flex: 2, child: Text('UTR NO', style: _headerColumn)), + Expanded(flex: 2, child: Text('RECEIVED', style: _headerColumn)), + ], + ), + ); + } + + Widget _dataRow(Map item, int sno) { + final highlightNo = _receivedIsNo(item); + // Fresh style (not copyWith on _dataBold): GoogleFonts may set `foreground`, + // which overrides `color` and can make text invisible on web. + final rowStyle = highlightNo + ? GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w400, + color: const Color(0xFF0F172A), + ) + : _dataBold; + + final row = Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 1, child: Text('$sno', style: rowStyle)), + Expanded( + flex: 2, + child: Text( + _cell(item, ['policy_no']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 2, + child: Text( + _cell(item, ['reg_no']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 1, + child: Text( + _cell(item, ['weight']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 2, + child: Text( + _cell(item, ['fuel_type']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 2, + child: Text( + _cell(item, ['vehicle_type']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 2, + child: Text( + _cell(item, ['premium']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 2, + child: Text( + _cell(item, ['commission_amount']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 2, + child: Text( + _cell(item, ['utr_no']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 2, + child: Text( + _cell(item, ['received_or_not']), + style: rowStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + + final padded = Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + child: row, + ); + + if (highlightNo) { + // Background in a separate layer so ListView/Container decoration never clips text (web). + return Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + color: _pendingChipBg, + borderRadius: BorderRadius.circular(8), + border: Border( + bottom: const BorderSide(color: _rowBorder, width: 1), + left: const BorderSide(color: _pendingChipBorder, width: 3), + ), + ), + ), + ), + padded, + ], + ); + } + + return Container( + clipBehavior: Clip.none, + decoration: BoxDecoration( + color: Colors.white, + border: const Border( + bottom: BorderSide(color: _rowBorder, width: 1), + ), + borderRadius: BorderRadius.circular(8), + ), + child: padded, + ); + } + + Widget _buildDataTable(BuildContext context) { + if (!_hasSearched) { + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + () { + final r = + (ref.watch(userRoleProvider) ?? '').toString().trim().toLowerCase(); + final hideAgent = r == 'agent' || r == 'a'; + return hideAgent + ? 'Choose filters (date range and/or payout raised) and tap Search to load the report.' + : 'Choose filters (date range, agent, and/or payout raised) and tap Search to load the report.'; + }(), + textAlign: TextAlign.center, + style: GoogleFonts.inter( + fontSize: 13, + color: const Color(0xFF64748B), + height: 1.4, + ), + ), + ), + ); + } + + if (isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + final pageItems = _paginated; + final startIndex = _filtered.isEmpty ? 0 : (currentPage - 1) * itemsPerPage; + + if (pageItems.isEmpty) { + return const SizedBox( + height: 50, + child: Center(child: Text('No available data')), + ); + } + + final isMobile = ResponsiveLayout.isMobile(context); + final tableWidth = + MediaQuery.of(context).size.width < 1500 ? 1500.0 : MediaQuery.of(context).size.width; + + if (isMobile) { + return Scrollbar( + thumbVisibility: true, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: tableWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _tableHeader(), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: pageItems.length, + itemBuilder: (context, i) { + return _dataRow(pageItems[i], startIndex + i + 1); + }, + ), + ], + ), + ), + ), + ); + } + + // One ListView inside a fixed-height box (no Column+Expanded) avoids relayout + // conflicts with horizontal SingleChildScrollView on web. + return LayoutBuilder( + builder: (context, constraints) { + final w = constraints.maxWidth < 1500 ? 1500.0 : constraints.maxWidth; + var h = constraints.maxHeight; + if (!h.isFinite || h <= 0) { + h = MediaQuery.sizeOf(context).height * 0.45; + } + return Scrollbar( + thumbVisibility: true, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: w, + height: h, + child: ListView( + padding: EdgeInsets.zero, + children: [ + _tableHeader(), + ...pageItems.asMap().entries.map((e) { + final i = e.key; + return _dataRow(e.value, startIndex + i + 1); + }), + ], + ), + ), + ), + ); + }, + ); + } + + /// Filters + table only (title row lives in [build] like [policylist]). + Widget _buildContent(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildFilterBar(context), + _buildSummaryChips(), + const SizedBox(height: 5), + ResponsiveLayout.isMobile(context) + ? _buildDataTable(context) + : Expanded(child: _buildDataTable(context)), + ], + ); + } + + @override + Widget build(BuildContext context) { + final isMobile = ResponsiveLayout.isMobile(context); + return MainLayout( + title: 'Payout Report', + // Single Column under MainLayout's Expanded — do not nest LayoutBuilder here; + // nested LayoutBuilder + Expanded caused _debugRelayoutBoundaryAlreadyMarkedNeedsLayout on web. + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + height: 30, + child: Align( + alignment: Alignment.centerLeft, + child: Text( + 'Payout Report', + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(height: 5), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: isMobile + ? SingleChildScrollView( + child: _buildContent(context), + ) + : _buildContent(context), + ), + ), + PaginationControls( + currentPage: currentPage, + itemsPerPage: itemsPerPage, + totalItems: _filtered.length, + onPageChanged: (page) { + setState(() => currentPage = page); + }, + onItemsPerPageChanged: (items) { + setState(() { + itemsPerPage = items; + currentPage = 1; + }); + }, + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/payout/payout_upload.dart b/lib/presentation/screens/payout/payout_upload.dart new file mode 100644 index 0000000..9a9ead8 --- /dev/null +++ b/lib/presentation/screens/payout/payout_upload.dart @@ -0,0 +1,720 @@ +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:nhance_partner/data/utils/toastNotification.dart'; + +import '../../../core/services/api_service.dart'; +import '../../layouts/main_layout.dart'; + +class PayoutUploadScreen extends ConsumerStatefulWidget { + const PayoutUploadScreen({super.key}); + + @override + ConsumerState createState() => _PayoutUploadScreenState(); +} + +class _PayoutUploadScreenState extends ConsumerState { + late ApiService apiService; + bool isLoading = false; + String? uploadedFileName; + List> failedRows = []; + /// Present when upload succeeded but `data.errors` was non-empty (partial success). + Map? partialSuccessData; + + @override + void initState() { + super.initState(); + apiService = ApiService(); + } + + void _resetAll() { + setState(() { + uploadedFileName = null; + failedRows = []; + partialSuccessData = null; + }); + } + + /// Parses `data.errors` (success-with-errors) or top-level error arrays. + List> _extractFailedRows(Map response) { + final dynamic data = response['data']; + if (data is Map) { + final err = data['errors']; + if (err is List) { + return err.map((e) { + if (e is Map) return Map.from(e); + if (e is Map) return Map.from(e); + return {'message': e.toString()}; + }).toList(); + } + } + final top = response['errors'] ?? response['failed_rows']; + if (top is List) { + return top.map((e) { + if (e is Map) return Map.from(e); + if (e is Map) return Map.from(e); + return {'message': e.toString()}; + }).toList(); + } + return []; + } + + String _failureMessage(Map response) { + final m = response['message']?.toString().trim(); + if (m != null && m.isNotEmpty) return m; + final data = response['data']; + if (data is String && data.trim().isNotEmpty) return data.trim(); + return 'Upload failed'; + } + + String _successSummary(Map data) { + final updated = data['updated_rows']; + final skipped = data['skipped_empty_payout']; + final parts = []; + if (updated != null) parts.add('Updated $updated row(s)'); + if (skipped != null) parts.add('Skipped empty payout: $skipped'); + return parts.isEmpty ? 'Upload successful' : parts.join(' · '); + } + + String _partialSummary(Map data) { + final base = _successSummary(data); + final ec = data['error_count']; + if (ec != null) return '$base · $ec row(s) with errors'; + return base; + } + + static const _errorIcon = Icon(Icons.error_outline, color: Colors.red, size: 16); + + Widget _messageWithLeadingIcon(String text) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 2), + child: _errorIcon, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + text, + style: GoogleFonts.inter(fontSize: 12, height: 1.35), + ), + ), + ], + ); + } + + void _handleUploadResponse(Map response) { + final status = response['status']?.toString(); + final code = response['code']; + + if (status == 'error' && + (response['message']?.toString().toLowerCase().contains('session') ?? false)) { + ToastHelper.showErrorToast(context, response['message']?.toString() ?? 'Session expired'); + return; + } + + final isHttpSuccess = + status == 'success' || + status == '200' || + code == 200 || + code == '200'; + + if (isHttpSuccess) { + final data = response['data']; + if (data is Map) { + final map = Map.from(data); + final errors = map['errors']; + if (errors is List && errors.isNotEmpty) { + setState(() { + partialSuccessData = map; + failedRows = errors.map((e) { + if (e is Map) return Map.from(e); + if (e is Map) return Map.from(e); + return {'message': e.toString()}; + }).toList(); + }); + ToastHelper.showWarningToast(context, _partialSummary(map)); + return; + } + ToastHelper.showSuccessToast(context, _successSummary(map)); + _resetAll(); + return; + } + ToastHelper.showSuccessToast( + context, + response['message']?.toString() ?? 'Upload successful', + ); + _resetAll(); + return; + } + + if (status == 'failed' || status == 'error' || !isHttpSuccess) { + final rows = _extractFailedRows(response); + ToastHelper.showErrorToast(context, _failureMessage(response)); + if (rows.isNotEmpty) { + setState(() { + failedRows = rows; + partialSuccessData = null; + }); + } + return; + } + + ToastHelper.showErrorToast(context, 'Unexpected response'); + } + + Future _uploadFile() async { + final picked = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['xlsx', 'xls'], + withData: true, + ); + if (picked == null || picked.files.isEmpty) return; + + final file = picked.files.single; + if (file.bytes == null || file.bytes!.isEmpty) { + ToastHelper.showWarningToast(context, 'Unable to read selected file'); + return; + } + + setState(() { + isLoading = true; + uploadedFileName = file.name; + }); + try { + final response = await apiService.uploadPolicyCommissionExcel( + file: file, + ); + _handleUploadResponse(response); + } catch (e) { + ToastHelper.showErrorToast(context, e.toString()); + } finally { + if (mounted) setState(() => isLoading = false); + } + } + + bool get _isStandardErrorShape { + if (failedRows.isEmpty) return false; + for (final r in failedRows) { + if (!r.containsKey('row') && !r.containsKey('policy_number')) return false; + } + return true; + } + + List _visibleColumns() { + if (failedRows.isEmpty) return []; + const preferred = ['row', 'policy_number', 'agent_code', 'expected_agent_code', 'message']; + const hidden = {}; + final keySet = {}; + for (final row in failedRows) { + keySet.addAll(row.keys); + } + final ordered = []; + for (final p in preferred) { + if (keySet.contains(p) && !hidden.contains(p)) ordered.add(p); + } + for (final k in keySet) { + if (!ordered.contains(k) && !hidden.contains(k)) ordered.add(k); + } + return ordered; + } + + Widget _buildPartialSummaryBanner() { + final d = partialSuccessData; + if (d == null) return const SizedBox.shrink(); + final updated = d['updated_rows']; + final skipped = d['skipped_empty_payout']; + final errCount = d['error_count']; + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Wrap( + spacing: 8, + runSpacing: 6, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (updated != null) + _summaryChip('Updated rows', updated.toString(), const Color(0xFF059669)), + if (skipped != null) + _summaryChip('Skipped empty payout', skipped.toString(), const Color(0xFF64748B)), + if (errCount != null) + _summaryChip('Errors', errCount.toString(), const Color(0xFFDC2626)), + ], + ), + ); + } + + Widget _summaryChip(String label, String value, Color color) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color.withOpacity(0.35)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$label: ', + style: GoogleFonts.inter(fontSize: 11, color: const Color(0xFF475569)), + ), + Text( + value, + style: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.w700, color: color), + ), + ], + ), + ); + } + + Widget _buildErrorsTable() { + if (_isStandardErrorShape) { + return Column( + children: [ + _buildTableHeaderRow(isStandard: true), + Expanded( + child: ListView.builder( + itemCount: failedRows.length, + itemBuilder: (context, index) { + final row = failedRows[index]; + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFE5E7EB)), + ), + ), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 52, + child: Text( + '${row['row'] ?? '-'}', + style: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.w600), + ), + ), + SizedBox( + width: 160, + child: Text( + (row['policy_number'] ?? '-').toString(), + style: GoogleFonts.inter(fontSize: 12), + ), + ), + SizedBox( + width: 100, + child: Text( + (row['agent_code'] ?? '-').toString().isEmpty + ? '—' + : (row['agent_code'] ?? '').toString(), + style: GoogleFonts.inter(fontSize: 12), + ), + ), + SizedBox( + width: 120, + child: Text( + row['expected_agent_code'] != null && + row['expected_agent_code'].toString().trim().isNotEmpty + ? row['expected_agent_code'].toString() + : '—', + style: GoogleFonts.inter(fontSize: 12, color: const Color(0xFF64748B)), + ), + ), + Expanded( + child: _messageWithLeadingIcon((row['message'] ?? '-').toString()), + ), + ], + ), + ); + }, + ), + ), + ], + ); + } + + final columns = _visibleColumns(); + return Column( + children: [ + _buildTableHeaderRow(isStandard: false, columns: columns), + Expanded( + child: ListView.builder( + itemCount: failedRows.length, + itemBuilder: (context, index) { + final row = failedRows[index]; + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFE5E7EB)), + ), + ), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final c in columns) + if (c == 'message') + Expanded( + child: _messageWithLeadingIcon((row[c] ?? '-').toString()), + ) + else + SizedBox( + width: 140, + child: Text( + (row[c] ?? '-').toString(), + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.inter(fontSize: 11), + ), + ), + ], + ), + ); + }, + ), + ), + ], + ); + } + + Widget _buildTableHeaderRow({required bool isStandard, List? columns}) { + TextStyle h() => GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w700, + color: const Color(0xFF334155), + letterSpacing: 0.3, + ); + if (isStandard) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + decoration: const BoxDecoration( + color: Color(0xFFF1F5F9), + border: Border(bottom: BorderSide(color: Color(0xFFE2E8F0))), + ), + child: Row( + children: [ + SizedBox(width: 52, child: Text('ROW', style: h())), + SizedBox(width: 160, child: Text('POLICY NUMBER', style: h())), + SizedBox(width: 100, child: Text('AGENT CODE', style: h())), + SizedBox(width: 120, child: Text('EXPECTED', style: h())), + Expanded(child: Text('MESSAGE', style: h())), + ], + ), + ); + } + final cols = columns ?? []; + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: const BoxDecoration( + color: Color(0xFFF1F5F9), + border: Border(bottom: BorderSide(color: Color(0xFFE2E8F0))), + ), + child: Row( + children: [ + for (final c in cols) + if (c == 'message') + Expanded( + child: Text( + c.toUpperCase().replaceAll('_', ' '), + style: h(), + ), + ) + else + SizedBox( + width: 140, + child: Text( + c.toUpperCase().replaceAll('_', ' '), + style: h(), + ), + ), + ], + ), + ); + } + + Future _onTemplateFileTap() async { + try { + await apiService.downloadCommissionUploadSampleExcel(); + } catch (e) { + if (mounted) { + ToastHelper.showErrorToast( + context, + 'Could not download template. Try again.', + ); + } + } + } + + Widget _buildTemplateDownloadHint() { + return Padding( + padding: const EdgeInsets.only(top: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Please download the sample file to review the format.', + textAlign: TextAlign.center, + style: GoogleFonts.inter( + fontSize: 12, + height: 1.4, + color: const Color(0xFF6B7280), + ), + ), + const SizedBox(height: 8), + MouseRegion( + cursor: SystemMouseCursors.click, + child: InkWell( + onTap: () => _onTemplateFileTap(), + borderRadius: BorderRadius.circular(4), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Text( + 'Template File', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: const Color(0xFF2E7D6E), + decoration: TextDecoration.underline, + decorationColor: const Color(0xFF2E7D6E), + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildUploadCard() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + InkWell( + onTap: isLoading ? null : _uploadFile, + borderRadius: BorderRadius.circular(10), + child: Container( + width: double.infinity, + constraints: const BoxConstraints(maxWidth: 1050), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 26), + decoration: BoxDecoration( + color: const Color(0xFFF8FAFA), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: const Color(0xFF3AA69A), width: 1), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: const Color(0xFFE6F6F4), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFF7BCBC3)), + ), + child: isLoading + ? const Padding( + padding: EdgeInsets.all(6), + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Color(0xFF2E7D6E)), + ), + ) + : const Icon( + Icons.upload_file_rounded, + size: 16, + color: Color(0xFF2E7D6E), + ), + ), + const SizedBox(height: 10), + Text( + isLoading ? 'Uploading file...' : 'Upload Your Payout Documents', + style: GoogleFonts.inter( + fontSize: 18, + fontWeight: FontWeight.w600, + color: const Color(0xFF111827), + ), + ), + const SizedBox(height: 4), + Text( + '(Supported Format: XLSX, XLS)', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w400, + color: const Color(0xFF6B7280), + ), + ), + const SizedBox(height: 12), + Text( + 'Click here to choose an Excel file and upload.', + style: GoogleFonts.inter( + fontSize: 12, + color: const Color(0xFF4B5563), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 16), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 1050), + child: SizedBox( + width: double.infinity, + child: _buildTemplateDownloadHint(), + ), + ), + ], + ); + } + + Widget _buildFailureGrid() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + uploadedFileName == null + ? 'Validation errors' + : 'File: $uploadedFileName', + style: GoogleFonts.inter(fontSize: 14, fontWeight: FontWeight.w600), + ), + ), + OutlinedButton.icon( + onPressed: isLoading ? null : _resetAll, + icon: const Icon(Icons.refresh, size: 16), + label: const Text('Re-upload'), + ), + ], + ), + _buildPartialSummaryBanner(), + const SizedBox(height: 4), + Text( + 'Error rows (${failedRows.length})', + style: GoogleFonts.inter(fontSize: 12, color: const Color(0xFF64748B)), + ), + const SizedBox(height: 8), + Expanded( + child: Container( + width: double.infinity, + decoration: BoxDecoration( + border: Border.all(color: const Color(0xFFE5E7EB)), + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.antiAlias, + child: LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth < 720 && _isStandardErrorShape) { + return ListView.builder( + padding: const EdgeInsets.all(12), + itemCount: failedRows.length, + itemBuilder: (context, index) { + final row = failedRows[index]; + return Container( + decoration: BoxDecoration( + color: const Color(0xFFF8FAFC), + borderRadius: BorderRadius.circular(8), + border: const Border( + bottom: BorderSide(color: Color(0xFFE5E7EB)), + ), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Row ${row['row'] ?? '-'}', + style: GoogleFonts.inter( + fontWeight: FontWeight.w700, + fontSize: 12, + ), + ), + const SizedBox(height: 6), + _mobileLine('Policy', row['policy_number']), + _mobileLine('Agent code', row['agent_code']), + if (row['expected_agent_code'] != null && + row['expected_agent_code'].toString().trim().isNotEmpty) + _mobileLine('Expected', row['expected_agent_code']), + const SizedBox(height: 6), + _messageWithLeadingIcon((row['message'] ?? '').toString()), + ], + ), + ), + ); + }, + ); + } + return _buildErrorsTable(); + }, + ), + ), + ), + ], + ); + } + + Widget _mobileLine(String label, dynamic value) { + final v = value?.toString().trim() ?? ''; + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 88, + child: Text( + '$label:', + style: GoogleFonts.inter(fontSize: 11, color: const Color(0xFF64748B)), + ), + ), + Expanded( + child: Text( + v.isEmpty ? '—' : v, + style: GoogleFonts.inter(fontSize: 12), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return MainLayout( + title: 'Payout Upload', + body: Container( + width: double.infinity, + color: Colors.white, + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Payout Upload', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w600, + color: const Color(0xFF111827), + ), + ), + const SizedBox(height: 10), + Expanded( + child: failedRows.isEmpty + ? Center(child: _buildUploadCard()) + : _buildFailureGrid(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/drawer_menu.dart b/lib/presentation/widgets/drawer_menu.dart index b7f6b07..529a3c2 100644 --- a/lib/presentation/widgets/drawer_menu.dart +++ b/lib/presentation/widgets/drawer_menu.dart @@ -205,6 +205,39 @@ class DrawerMenuState extends ConsumerState { context.go(AppRoutes.allStaffAttendance); }, ), + + if (roleId == 'Accounts') ...[ + _buildMenuItem( + context: context, + icon: const Icon(Icons.payments_outlined, size: 30, color: Colors.black), + label: "Payout", + isActive: currentPath == AppRoutes.payoutList, + onTap: () => context.go(AppRoutes.payoutList), + ), + _buildMenuItem( + context: context, + icon: const Icon(Icons.upload_file_outlined, size: 30, color: Colors.black), + label: "Payout Upload", + isActive: currentPath == AppRoutes.payoutUpload, + onTap: () => context.go(AppRoutes.payoutUpload), + ), + _buildMenuItem( + context: context, + icon: const Icon(Icons.table_chart_outlined, size: 30, color: Colors.black), + label: "Payout Report", + isActive: currentPath == AppRoutes.payoutReport, + onTap: () => context.go(AppRoutes.payoutReport), + ), + ], + + if (roleId == 'agent') + _buildMenuItem( + context: context, + icon: const Icon(Icons.table_chart_outlined, size: 30, color: Colors.black), + label: "Payout Report", + isActive: currentPath == AppRoutes.payoutReport, + onTap: () => context.go(AppRoutes.payoutReport), + ), ], ), );