import 'dart:convert'; 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:http/http.dart' as http; import 'package:nhance_partner/presentation/providers/userRoleProvider.dart'; import 'package:toastification/toastification.dart'; import '../../../../core/config/env.dart'; import '../../../../core/services/api_service.dart'; import '../../../../data/services/auth_service.dart'; import '../../../../data/utils/toastNotification.dart'; import '../../../../data/utils/validators.dart'; import '../../layouts/responsive_layout.dart'; import '../../providers/manager_provider.dart'; import '../../themes/indicators/input_field_decoration.dart'; import '../../themes/indicators/text_field_theme.dart'; // 🔹 Custom Dialog Widget class AssignStaffDialog extends ConsumerStatefulWidget { final dynamic userId; final dynamic regNum; final dynamic enquiryPrimaryId; final void Function(String value) onSubmit; const AssignStaffDialog({ super.key, required this.onSubmit, required this.regNum, required this.userId, required this.enquiryPrimaryId, }); @override // State createState() => _AddDialogState(); ConsumerState createState() => _AddDialogState(); } class _AddDialogState extends ConsumerState { late ApiService apiService; String? _token; dynamic role; bool isLoading = false; bool showSuccess = false; late TextEditingController controller; Map controllers = {}; List> getInsurersData = []; List> filteredInsurersData = []; String? selectedInsurer; List> getBrokerData = []; List> filteredBrokerData = []; String? selectedBroker; final _formKey = GlobalKey(); final _formKeyEndrosment = GlobalKey(); final GlobalKey>> dropDownKey = GlobalKey>>(); List tabHeader = ['regNum']; final GlobalKey>> dropDownKeyInsurerEnqAsgn = GlobalKey>>(); final GlobalKey>> dropDownKeyBroker = GlobalKey>>(); List> getStaffDetailsDataEnqAsgn = []; List> filteredStaffDataEnqAsgn = []; String? selectedStaff; String? selectedRegNum; String? selectedStaffName; dynamic managerId; Map dataDetails() { final data = { "id": widget.enquiryPrimaryId, "assigned_to": selectedStaff, "insurer_id": selectedInsurer, "broker_id": selectedBroker, "updated_by": widget.userId, }; return data; } @override void initState() { super.initState(); apiService = ApiService(); for (String field in tabHeader) { controllers[field] = TextEditingController(); } controllers["regNum"]?.text = widget.regNum; selectedRegNum = widget.regNum; // 🔹 Init logic here (API calls, token fetch, etc.) _initializeToken(); // getStaffDetailsForEnquiryAssignment(1); getInsurers(); getBroker(); Future.microtask(() { managerId = ref.watch(managerIdProvider); // final handlerId = ref.watch(handlerIdProvider); role = ref.watch(userRoleProvider); final userID = ref.watch(userIdProvider); print("managerId - $managerId"); if (userID != null) { print('hansles'); getStaffDetailsForEnquiryAssignment(userID); } }); } Future _initializeToken() async { _token = await AuthService.getToken(); print("APISERTOKEN - $_token"); } Future getInsurers() async { print('Insurers called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('Insurers'); if (response['status'] == 200) { print('getInsurers - ${response['data']}'); setState(() { getInsurersData = List>.from(response['data']); print('API Data - $getInsurersData'); filteredInsurersData = List.from(getInsurersData); // print('originalData - $filteredInsurersData'); }); } else { getInsurersData = []; filteredInsurersData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getBroker() async { print('getBroker called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('Broker'); if (response['status'] == 200) { print('getBroker - ${response['data']}'); setState(() { getBrokerData = List>.from(response['data']); print('API Data - $getBrokerData'); filteredBrokerData = List.from(getBrokerData); // print('originalData - $filteredBrokerData'); }); } else { getBrokerData = []; filteredBrokerData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getStaffDetailsForEnquiryAssignment(int id) async { print('getStaffDetailsForEnquiryAssignment called By handler'); setState(() { isLoading = true; }); try { // final response = await apiService.fetchStaffUserList(id, role); final response = await apiService.fetchStaffListForEnquiryAssignDropDown( managerId, id, role, ); if (response['status'] == 'success') { print('getStaffDetailsForEnquiryAssignment - ${response['data']}'); setState(() { getStaffDetailsDataEnqAsgn = List>.from( response['data'], ); print('API Data - $getStaffDetailsDataEnqAsgn'); filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn); // print('originalData - $filteredStaffDataEnqAsgn'); }); } else { getStaffDetailsDataEnqAsgn = []; filteredStaffDataEnqAsgn = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } void handleDone() { if (!_formKey.currentState!.validate()) return; setState(() { if (_formKey.currentState!.validate()) { dataDetails(); final dataSet = dataDetails(); print("b4 - $dataSet"); dataSet['from_dashboard_enquiry_status'] = 'Assigned'; print("Atr - $dataSet"); createUserData(dataSet); } else { // isDi sable = false; } }); } Future createUserData(data) async { // final bool isUpdating = widget.id != null && widget.id != 'create'; final String apiUrldata; apiUrldata = '${Env.apiUrl}enquiry/enquiryAssignUpdate'; // final token = await getToken(); // Fetch token if (_token == null) { throw Exception('Token not found. Please log in.'); } print("data------- $data}"); try { final response = await http.post( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $_token', 'Content-Type': 'application/json', 'app-signature': Env.App_Signature, }, body: jsonEncode(data), // Convert map to JSON ); if (response.statusCode == 200) { print("Staff submitted successfully!"); print("Response: ${response.body}"); ToastHelper.showSuccessToast(context, 'Saved Successfully'); setState(() { showSuccess = true; }); // Navigator.of(context).pop(); // widget.onSubmit("success"); // context.go(AppRoutes.staffLst); } else if (response.statusCode == 403) { await apiService.clearLocalStorageAndRedirect(); } else { final responseBody = jsonDecode(response.body); dynamic msg = responseBody['data']; print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); ToastHelper.showErrorToast(context, 'Failed To Save'); } } catch (e) { print(" Error submitting Staff: $e"); } } @override void dispose() { // controllers.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SelectionArea( child:AlertDialog( backgroundColor: Colors.white, content: showSuccess ? Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: () => Navigator.pop(context), child: Container( padding: const EdgeInsets.all(5.0), decoration: BoxDecoration( color: const Color(0xFFF1F1F1), borderRadius: BorderRadius.circular(5.0), ), child: Tooltip( message: 'Close', child: const Icon(Icons.close, size: 18), ), ), ), ], ), const SizedBox(height: 16), successContent(context), ], ) : Column( mainAxisSize: MainAxisSize.min, children: [ // Header row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Assign Enquiry To Staff', style: GoogleFonts.inter( color: const Color(0xFF374141), fontSize: 14, fontWeight: FontWeight.bold, ), ), GestureDetector( onTap: () => Navigator.pop(context), child: Container( padding: const EdgeInsets.all(5.0), decoration: BoxDecoration( color: const Color(0xFFF1F1F1), borderRadius: BorderRadius.circular(5.0), ), child: Tooltip( message: 'Close', child: const Icon(Icons.close, size: 18), ), ), ), ], ), const SizedBox(height: 16), // 🔹 Switch content dynamically claims(context), ], ), actions: [ if (!showSuccess) Center( child: GestureDetector( onTap: () { handleDone(); // widget.onSubmit(controller.text.trim()); // Navigator.of(context).pop(); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 25.0, vertical: 8, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.0), color: const Color(0xFF425B5B), ), child: const Text( 'Assign', style: TextStyle(color: Colors.white), ), ), ), ), ], ),); } // 🔹 Example: Claims widget Widget claims(BuildContext context) { return Form( key: _formKey, child: Column( children: [ buildRegistrationNumber(context), buildSelectStaffMem(context), buildInsurer(context), buildBroker(context), ], ), ); } Widget successContent(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( "assets/miscellaneous/success_img1.png", height: 60, width: 60, ), SizedBox(height: 15), Text('Vehicle policy successfully assigned ', style: _successText), Text( 'to $selectedStaffName for vehicle $selectedRegNum', style: _successText, ), ], ); } // ------------------------- Widget Part ----------------------------------- Widget buildRegistrationNumber(context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Vehicle Number", style: _textStyle), SizedBox(height: 10), ThemedFormField( controller: controllers['regNum']!, backgroundColor: Color(0xFFEDF6F5), // validator: (value) => Validators.requiredField(value, "name"), txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.26, readOnly: true, ), ], ); } Widget buildSelectStaffMem(ctx) { // Map? selectedVehicle = filteredStaffDataEnqAsgn.firstWhere( // (item) => item['id'].toString() == selectedStaff, // orElse: () => {}, // ); Map? selectedVehicle; try { selectedVehicle = filteredStaffDataEnqAsgn.firstWhere( (item) => item['id'].toString() == selectedStaff, ); } catch (e) { selectedVehicle = null; // ✅ fallback } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Select Staff Member *", style: _textStyle), SizedBox(height: 10), Container( color: Colors.white, width: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.26, // height: 40, child: DropdownSearch>( key: dropDownKey, // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, selectedItem: selectedVehicle, items: (filter, infiniteScrollProps) { return filteredStaffDataEnqAsgn; }, itemAsString: (val) => val['name'].toString(), compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, // dropdownBuilder: (context, selectedItem) => Align( // alignment: Alignment.centerLeft, // // child: Text( // selectedItem != null ? selectedItem['name'].toString() : "", // style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), // overflow: TextOverflow.ellipsis, // maxLines: 1, // softWrap: false, // ), // ), decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Staff Member", ).copyWith( filled: true, fillColor: Color( 0xFFEDF6F5,), // 👈 makes the dropdown input white isDense: true, // 👈 Makes the field compact contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 0), // 👈 Vertical 0 helps center the text ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Staff ...", enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), // constraints: BoxConstraints(), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 3, ), child: Text( item['name'].toString(), style: GoogleFonts.inter(fontSize: 14, color: Colors.black), ), ); }, ), onChanged: (val) { if (val != null) { print("Selected Staff : ${val['name']}"); print("Id: ${val['id']}"); selectedStaffName = val['name']; selectedStaff = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ], ); } Widget buildInsurer(BuildContext context) { Map? selectedInsurerd = filteredInsurersData.firstWhere( (item) => item['id'].toString() == selectedInsurer, orElse: () => {}, ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Insurer *', style: _textStyle), SizedBox(height: 10), Container( decoration: BoxDecoration( color: Colors.white, // borderRadius: BorderRadius.circular(10.0), ), width: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.26, // height: 40, child: DropdownSearch>( key: dropDownKeyInsurerEnqAsgn, selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, items: (filter, infiniteScrollProps) { return filteredInsurersData; }, itemAsString: (val) => val['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Insurer", ).copyWith( filled: true, fillColor: Color(0xFFEDF6F5), // 👈 makes the dropdown input white isDense: true, // 👈 Makes the field compact contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 0), // 👈 Vertical 0 helps center the text ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Insurer...", enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), child: Text( item['name'].toString(), style: GoogleFonts.inter(fontSize: 14, color: Colors.black), ), ); }, // constraints: BoxConstraints(), ), onChanged: (val) { if (val != null) { print("Selected Insurer : ${val['name']}"); print("Id: ${val['id']}"); selectedInsurer = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ], ); } Widget buildBroker(BuildContext context) { Map? selectedBrokers = filteredBrokerData.firstWhere( (item) => item['id'].toString() == selectedBroker, orElse: () => {}, ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Broker *', style: _textStyle), SizedBox(height: 10), Container( decoration: BoxDecoration( color: Colors.white, // borderRadius: BorderRadius.circular(10.0), ), width: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.26, // height: 40, child: DropdownSearch>( key: dropDownKeyBroker, selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null, items: (filter, infiniteScrollProps) { return filteredBrokerData; }, itemAsString: (val) => val['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Broker", ).copyWith( filled: true, fillColor: Color(0xFFEDF6F5,), // 👈 makes the dropdown input white isDense: true, // 👈 Makes the field compact contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 0), // 👈 Vertical 0 helps center the text ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Broker...", enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), child: Text( item['name'].toString(), style: GoogleFonts.inter(fontSize: 14, color: Colors.black), ), ); }, // constraints: BoxConstraints(), ), onChanged: (val) { if (val != null) { print("Selected Broker : ${val['name']}"); print("Id: ${val['id']}"); selectedBroker = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ], ); } // ------------------- STyle --------------------------------- static final TextStyle _textStyle = TextStyle( fontSize: 12, fontWeight: FontWeight.w600, ); static final TextStyle _successText = TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF177126), ); }