import 'dart:convert'; // import 'dart:io' as html; import 'dart:typed_data'; // Import for Uint8List import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:nhance_partner/core/routing/routes.dart'; import 'package:nhance_partner/data/utils/toastNotification.dart'; import 'package:nhance_partner/presentation/themes/indicators/text_field_theme.dart'; import '../../../../core/config/env.dart'; import '../../../../core/services/api_service.dart'; import '../../../../data/services/auth_service.dart'; import '../../../../data/utils/validators.dart'; import '../../../layouts/main_layout.dart'; import '../../../layouts/responsive_layout.dart'; import '../../../providers/manager_provider.dart'; import 'package:universal_html/html.dart' as html; import '../../../themes/indicators/input_field_decoration.dart'; class Staff extends ConsumerStatefulWidget { final String? id; final void Function(String value) onSubmit; const Staff({super.key, this.id, required this.onSubmit}); @override ConsumerState createState() => StaffState(); } class StaffState extends ConsumerState { final _formKey = GlobalKey(); late ApiService apiService; final GlobalKey>> dropDownKey = GlobalKey>>(); final GlobalKey>> dropDownKeyHandler = GlobalKey>>(); List tabHeader = ['name', 'email', 'mobile', 'code']; late String isActive = "1"; html.File? passportFile; String? selectedFileNames; String? passportFileUrlFromApi; String? selectedId; String? _token; bool isLoading = false; bool showHandler = false; String? selectedRole; List> filteredRolesData = []; List> getRolesData = []; String? selectedHandler; List? selectedHandlerIds = []; List> filteredHandlersData = []; List> getHandlersData = []; Map controllers = {}; dynamic userId; dynamic managerId; @override void initState() { super.initState(); apiService = ApiService(); for (String field in tabHeader) { controllers[field] = TextEditingController(); } _initializeToken(); updateData(); getRole(); Future.microtask(() { managerId = ref.watch(managerIdProvider); userId = ref.watch(userIdProvider); if (managerId != null) { print('hansles'); getHandlers(managerId); } }); } Map dataDetails() { final data = { "name": controllers["name"]?.text, "email": controllers["email"]?.text, "mobile": controllers["mobile"]?.text, // "emp_id": controllers["code"]?.text, "is_active": isActive, "role_id": selectedRole, // "handler_id": selectedHandler, "handler_id": selectedHandlerIds, // Always send mapped manager id: // 1) Secondary Manager -> mapped Manager ID // 2) All other roles -> mapped Manager ID "manager_id": managerId, }; return data; } Future _initializeToken() async { _token = await AuthService.getToken(); print("APISERTOKEN - $_token"); } void updateData() async { if (widget.id != null && widget.id != 'Create') { dynamic response = await apiService.findSingleStaffData(widget.id!); final data = response['data']; print("updateData - ${response['data']}"); if (data == null) return; setState(() { selectedId = widget.id; controllers['name']?.text = data['name'] ?? ''; controllers['email']?.text = data['email'] ?? ''; controllers['mobile']?.text = data['mobile'] ?? ''; selectedRole = data['role_id'] ?? ''; // selectedHandler = data['handler_id'] ?? ''; if (data['handler_id'] != null && data['handler_id'].toString().isNotEmpty) { try { // Decode only if it's a valid JSON array string selectedHandlerIds = jsonDecode(data['handler_id']); } catch (e) { // Fallback: handle if it's not JSON (e.g., already a list) if (data['handler_id'] is List) { selectedHandlerIds = (data['handler_id'] as List) .map((e) => e.toString()) .toList(); } else { selectedHandlerIds = []; } } } // controllers['code']?.text = data['emp_id'] ?? ''; isActive = data["is_active"]; String? apiDocPath = data["certificate_file_name"]; if (apiDocPath != null && apiDocPath.isNotEmpty) { selectedFileNames = apiDocPath.split('/').last; passportFileUrlFromApi = apiDocPath; passportFile = null; } else { selectedFileNames = null; passportFile = null; passportFileUrlFromApi = null; } }); } } Future handleSave() async { if (!_formKey.currentState!.validate()) return; final email = controllers['email']!.text.trim(); final phone = controllers['mobile']!.text.trim(); // 🔴 Conditional validation: at least one required if (email.isEmpty && phone.isEmpty) { ToastHelper.showSuccessToast( context, 'Please enter either Email or Phone Number', ); return; } // Prevent submit without mapped manager id. if (managerId == null || managerId.toString().trim().isEmpty) { print('Manager mapping missing. managerId: $managerId, userId: $userId'); ToastHelper.showSuccessToast( context, 'Manager is missing. Please map a manager and try again.', ); return; } setState(() { if (_formKey.currentState!.validate()) { dataDetails(); final dataSet = dataDetails(); print("dataSetAgent - $dataSet"); createUserData(dataSet); } else { // isDisable = false; } }); } Future createUserData(data) async { final bool isUpdating = widget.id != null && widget.id != 'Create'; final String? id = isUpdating ? widget.id : null; final String apiUrldata; apiUrldata = isUpdating ? '${Env.apiUrl}staff/updateStaff' : '${Env.apiUrl}staff/createStaff'; // final token = await getToken(); // Fetch token if (_token == null) { throw Exception('Token not found. Please log in.'); } if (isUpdating) { data['id'] = id; // Add plan_id for update data['updated_by'] = userId; } else { data['created_by'] = userId; } print("data------- $data}"); try { final response = await http.post( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $_token', 'Content-Type': 'application/json', 'app-signature': Env.App_Signature, }, body: jsonEncode(data), // Convert map to JSON ); if (response.statusCode == 200) { print("Staff submitted successfully!"); print("Response: ${response.body}"); isUpdating ? ToastHelper.showSuccessToast( context, 'Staff Updated Successfully', ) : ToastHelper.showSuccessToast( context, 'Staff Created Successfully', ); 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}"); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text("Staff User Creation Failed"), content: Text(msg), actions: [ TextButton( child: Text("OK"), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } } catch (e) { print(" Error submitting Staff: $e"); } } Future getRole() async { print('Handlers called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('staffRole'); if (response['status'] == 200) { print('getRole - ${response['data']}'); setState(() { getRolesData = List>.from(response['data']); print('API Data - $getRolesData'); filteredRolesData = List.from(getRolesData); print('originalData - $filteredRolesData'); }); } else { getRolesData = []; filteredRolesData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getHandlers(managerId) async { print('Handlers called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchHandlerNameDropDown(managerId); if (response['code'] == 200 || response['status'] == 'success') { print('getHandlers -'); print('getHandlers - ${response['data']}'); setState(() { getHandlersData = List>.from(response['data']); print('API HandlersData - $getHandlersData'); filteredHandlersData = List.from(getHandlersData); print('HandlersoriginalData - $filteredHandlersData'); }); } else { getHandlersData = []; filteredHandlersData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } @override void dispose() { for (var controller in controllers.values) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return SelectionArea( child:AlertDialog( backgroundColor: Colors.white, content: Container( // color: Colors.yellow.shade50, width: MediaQuery.of(context).size.width * 0.62, height: MediaQuery.of(context).size.height * 0.65, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Container( // height: 30, // color: Colors.red.shade50, width: MediaQuery.of(context).size.width, child: GestureDetector( onTap: () { context.go(AppRoutes.staffLst); }, child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start, children: [ // Tooltip( // message: 'Back', // child: IconButton( // icon: const Icon( // Icons.arrow_left_sharp, // size: 25, // color: Color(0xFF425B5B), // ), // onPressed: () { // context.go(AppRoutes.staffLst); // }, // splashRadius: 28, // hoverColor: Colors.black12, // padding: const EdgeInsets.all(8), // constraints: const BoxConstraints(), // ), // ), const SizedBox(width: 5), // spacing between icon and text Text( "Staff", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, ), ), Spacer(), 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), ), ), ), ], ), ), ), // SizedBox(height: 10), Expanded( child: Container( // color: Colors.green, width: MediaQuery.of(context).size.width, margin: EdgeInsets.all(8.0), padding: EdgeInsets.symmetric(horizontal: 14.0, vertical: 20.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.0), color: Colors.white, // color: Color(0xFFEDF6F5), ), child: Column( children: [ Row(children: [Expanded(child: buildFormFields())]), Spacer(), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: () { handleSave(); }, child: Container( padding: EdgeInsets.symmetric( horizontal: 45.0, vertical: 8, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.0), color: Color(0xFF2E7D6E), // color: Color(0xFF425B5B), ), child: Text( 'Save', style: GoogleFonts.poppins( color: Colors.white, fontSize: 12, ), ), ), ), ], ), SizedBox(height: 30), ], ), ), ), SizedBox(height: 10), ], ), ), ),); } Widget buildFormFields() { return Form( key: _formKey, child: Column( children: [ Row( children: [ buildName(), SizedBox(width: 25), buildEmail(), SizedBox(width: 25), buildPhNumber(), ], ), SizedBox(height: 20), Row( children: [ buildRole(context), SizedBox(width: 25), if (showHandler) ...[ Row( children: [ buildHandler(context), SizedBox(width: 25), SizedBox.shrink(), ], ), ], ], ), // Row( // children: [ // Expanded(child: buildAddress()), // SizedBox(width: 25), // Expanded(child: SizedBox.shrink()), // ], // ), ], ), ); } Widget buildName() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Full Name *", style: _textStyle), SizedBox(height: 10), ThemedFormField( borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), controller: controllers['name']!, validator: (value) => Validators.requiredField(value, "name"), txtwidth: MediaQuery.of(context).size.width * 0.18, ), ], ); } Widget buildEmail() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Email *", style: _textStyle), SizedBox(height: 10), ThemedFormField( controller: controllers['email']!, borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), // validator: (value) => Validators.email(value, "email"), validator: (value) { final phone = controllers['mobile']!.text.trim(); if (value!.isEmpty && phone.isNotEmpty) { return null; // phone is given → email not required } return Validators.email(value, "email"); }, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')), ], txtwidth: MediaQuery.of(context).size.width * 0.18, ), ], ); } Widget buildPhNumber() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Phone Number *", style: _textStyle), SizedBox(height: 10), ThemedFormField( controller: controllers['mobile']!, borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), // validator: (value) => Validators.phone(value, "phNumber"), validator: (value) { final email = controllers['email']!.text.trim(); if (value!.isEmpty && email.isNotEmpty) { return null; // email is given → phone not required } return Validators.phone(value, "phNumber"); }, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), ], txtwidth: MediaQuery.of(context).size.width * 0.18, ), ], ); } Widget buildId() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Staff Id ", style: _textStyle), SizedBox(height: 10), ThemedFormField( controller: controllers['code']!, borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), // validator: (value) => Validators.requiredField(value, "id"), txtwidth: MediaQuery.of(context).size.width * 0.18, ), ], ); } // Widget buildAddress() { // return Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // Text("Role*", style: _textStyle), // SizedBox(width: 10), // ThemedFormField( // controller: controllers['address']!, // // txtwidth: MediaQuery.of(context).size.width * 0.18, // ), // ], // ); // } Widget buildRole(BuildContext context) { Map? selectedroleVal = filteredRolesData.firstWhere( (item) => item['id'].toString() == selectedRole, orElse: () => {}, ); if (selectedroleVal.isNotEmpty && selectedroleVal['role'] != null && selectedroleVal['role'] == 'Staff') { showHandler = true; } else { selectedHandlerIds = []; showHandler = false; } final isReadOnly = widget.id != null && widget.id != 'Create'; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Role *", 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.18, // height: 40, child: AbsorbPointer( absorbing: isReadOnly, child: DropdownSearch>( key: dropDownKey, selectedItem: selectedroleVal.isNotEmpty ? selectedroleVal : null, items: (filter, infiniteScrollProps) { return filteredRolesData; }, itemAsString: (val) => val['role'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Role", ).copyWith( filled: true, fillColor: Colors.white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0xFFE2E8F0), width: 1, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0xFFE2E8F0), width: 1, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( autofocus: true, decoration: InputDecoration( contentPadding: EdgeInsets.all(3), filled: true, fillColor: Colors.white, hintText: "Select Role...", hintStyle: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFE2E8F0), // color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFE2E8F0), // color: Colors.white, width: 1.5, ), // 👈 Focused border ), enabled: widget.id == null, ), ), // constraints: BoxConstraints(), ), onChanged: widget.id != null && widget.id != 'Create' ? null : (val) { if (val != null) { print("Selected Role : ${val['role']}"); print("Id: ${val['id']}"); selectedRole = val['id']; print('selectedRole - ${val['role']}'); if (val['role'] == 'Staff') { setState(() { showHandler = true; }); } else { setState(() { showHandler = false; }); } print('showHandler - $showHandler'); // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ), ], ); } Widget buildHandler(BuildContext context) { Map? selectedHandlered = filteredHandlersData.firstWhere( (item) => item['id'].toString() == selectedHandler, orElse: () => {}, ); // final isReadOnly = widget.id != null; final isReadOnly = false; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Handler Name *", 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.18, // height: 40, child: AbsorbPointer( absorbing: isReadOnly, // child: DropdownSearch>( child: DropdownSearch>.multiSelection( key: dropDownKeyHandler, // selectedItem: selectedHandlered.isNotEmpty // ? selectedHandlered // : null, selectedItems: filteredHandlersData .where( (item) => (selectedHandlerIds ?? []).contains(item['id']), ) .toList(), items: (filter, infiniteScrollProps) { return filteredHandlersData; }, itemAsString: (val) => val['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id // validator: (val) { // if (val == null) { // return "Required"; // ✅ error message // } // return null; // }, validator: (val) { if (val == null || val.isEmpty) { return "Required"; } return null; }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Handler", ).copyWith( hintStyle: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), filled: true, fillColor: Colors.white, // 👈 makes the dropdown input white enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFE2E8F0), // color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFE2E8F0), // color: Colors.white, width: 1.5, ), // 👈 Focused border ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), popupProps: PopupPropsMultiSelection.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), showSearchBox: true, menuProps: MenuProps(backgroundColor: Colors.white), searchFieldProps: TextFieldProps( autofocus: true, decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Handler...", hintStyle: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.white), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( // color: Colors.blue, color: Color(0xFFEDF6F5), width: 1.5, ), ), ), ), // checkBoxBuilder: (context, item, isSelected, isDisabled) { // return Icon( // isSelected // ? Icons.check_box // : Icons.check_box_outline_blank, // color: isSelected ? Color(0xFFE26728) : Colors.red, // ); // }, // validationBuilder: (context, selectedItems) { // return Padding( // padding: const EdgeInsets.symmetric( // vertical: 8.0, // horizontal: 8.0, // ), // child: Row( // mainAxisAlignment: MainAxisAlignment.end, // children: [ // ElevatedButton( // style: ElevatedButton.styleFrom( // backgroundColor: Color(0xFF425B5B), // foregroundColor: Colors.white, // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8), // ), // minimumSize: Size(70, 36), // ), // onPressed: () => Navigator.pop(context), // child: const Text("OK"), // ), // ], // ), // ); // }, ), onChanged: (List> selectedVals) { selectedHandlerIds = selectedVals .map((v) => v['id'].toString()) .toList(); print("Selected Handler IDs: $selectedHandlerIds"); }, // popupProps: PopupProps.menu( // fit: FlexFit.loose, // constraints: BoxConstraints(maxHeight: 250), // menuProps: MenuProps( // backgroundColor: // Colors.white, // 👈 sets dropdown background to white // ), // showSearchBox: true, // searchFieldProps: TextFieldProps( // decoration: InputDecoration( // filled: true, // fillColor: Colors.white, // hintText: "Search Handler...", // enabledBorder: OutlineInputBorder( // borderSide: BorderSide( // color: Colors.white, // ), // 👈 Normal border // ), // focusedBorder: OutlineInputBorder( // borderSide: BorderSide( // color: Colors.white, // width: 1.5, // ), // 👈 Focused border // ), // ), // ), // // constraints: BoxConstraints(), // ), // // onChanged: (val) { // if (val != null) { // print("Selected Handler : ${val['name']}"); // print("Id: ${val['id']}"); // selectedHandler = val['id']; // // controllers['agentId']?.text = val['agent_code']; // // agentId = agent['id']; // } // }, ), ), ), ], ); } static final _textStyle = GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, ); }