import 'dart:convert'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import '../../config/apiUrl.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../widgets/custom_text_forex.dart'; import 'groupList.dart'; class GroupData extends StatefulWidget { final Future Function() fetchGetGroup; final bool isDesktop; final Color? layoutColor; final int? groupId; // <-- Add this final Map? groupData; const GroupData({ super.key, required this.isDesktop, this.layoutColor, required this.fetchGetGroup, this.groupId, this.groupData, }); @override GroupDataState createState() => GroupDataState(); } class GroupDataState extends State { final ApiService apiService = ApiService(); Map DomesticMap = {}; Map InternationalMap = {}; late List? apiForDomestic; late List? apiForInternational; Map? apiData; final Map controllers = {}; Map focusNodes = {}; Map focusStates = {}; Map errorMessages = {}; List domesticList = []; List internationalList = []; String? selectedName; String? selectedDescription; String? selectedInternationalPolicyID; String? selectedInternationalPolicyName; String? selectedDomesticPolicyID; String? selectedDomesticPolicyName; String? userId; int? groupDataId; late String isActive = "1"; bool isDisable = false; List dataHeader = [ "name", "description", "domestic_policy_id", "domestic_policy_name", "international_policy_id", "international_policy_name", ]; Map group_Detials() { final data = { "name": controllers["name"]?.text, "description": controllers["description"]?.text, "domestic_policy_id": selectedDomesticPolicyID, "international_policy_id": selectedInternationalPolicyID, "domestic_policy_name": selectedDomesticPolicyName, "international_policy_name": selectedInternationalPolicyName, "is_active": isActive, }; return data; } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { loadAllServices(); }); apiForDomestic = null; apiForInternational = null; apiData = null; // for (var field in dataHeader) { // controllers[field] = TextEditingController(); // } for (var field in dataHeader) { controllers[field] = TextEditingController(); focusNodes["${field}FocusNode"] = FocusNode(); focusStates["${field}Focused"] = false; } print("Focus Nodes KeysII: ${focusNodes.keys.toList()}"); print("Focus States KeysII: ${focusStates.keys.toList()}"); print("Text Controllers KeysII: ${controllers.keys.toList()}"); for (var key in focusNodes.keys) { _addFocusListener(focusNodes[key]!, (focus) { setState(() { focusStates[key.replaceFirst("FocusNode", "Focused")] = focus; }); }); } if (widget.groupId != null) { print('Editing group ID: ${widget.groupId}'); updateGroupDetails(); } _clearError(); } void _clearError() { setState(() { errorMessages.clear(); }); } @override void dispose() { for (var node in focusNodes.values) { node.dispose(); } for (var controller in controllers.values) { controller.dispose(); } super.dispose(); } void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { updateState(node.hasFocus); }); }); } void updateGroupDetails() { print("Update - ${widget.groupData}"); final data = widget.groupData; if (data == null) return; setState(() { selectedDomesticPolicyID = data['domestic_policy_id']; // For dropdown selectedDomesticPolicyName = ""; selectedInternationalPolicyID = data['international_policy_id']; selectedInternationalPolicyName = ""; // Optional if used elsewhere controllers['name']?.text = data['name'] ?? ''; controllers['description']?.text = data['description'] ?? ''; isActive = data["is_active"]; final groupId = int.tryParse(data['group_id'].toString()); groupDataId = groupId; }); } Future loadAllServices() async { try { final result = await apiService.fetchAllPolicy(context); userId = await getUserId(); final roleUser = await getRoleUser(); if (roleUser != null && (roleUser == 'Org Admin' || roleUser == 'Travel Admin')) { setState(() { apiForDomestic = result.where((policy) => policy["domestic"] == "1").toList(); apiForInternational = result.where((policy) => policy["international"] == "1").toList(); }); print("Domestic policies: $apiForDomestic"); print("International policies: $apiForInternational"); } else { apiService.logout(context); } } catch (e) { print('Error fetching role list: $e'); } } void toggleStatus() { setState(() { isActive = isActive == "1" ? "0" : "1"; }); } bool validateData() { errorMessages.clear(); final data = { "name": controllers["name"]?.text, "description": controllers["description"]?.text, "domestic_policy_id": selectedDomesticPolicyID, "international_policy_id": selectedInternationalPolicyID, "domestic_policy_name": selectedDomesticPolicyName, "international_policy_name": selectedInternationalPolicyName, }; final requiredFields = ["name", "description"]; // Check validation for each field for (String field in requiredFields) { if (data[field] == null || data[field].toString().trim().isEmpty) { errorMessages[field] = "Required"; } } return errorMessages.isEmpty; } Future handleSubmit() async { userId = await getUserId(); setState(() { isDisable = true; // This triggers UI rebuild with error messages if (validateData()) { postGroupData(); } else { isDisable = false; } }); final groupData1 = group_Detials(); print("GroupDAta - $groupData1"); } Future postGroupData({int isActive = 1}) async { final orgId = await getOrgId(); final groupData = group_Detials(); final String apiUrlData; // static here groupData["org_id"] = orgId; // dynamic here if (groupDataId != null) { apiUrlData = '$apiUrl/api/groups/update/$groupDataId'; groupData["group_id"] = groupDataId.toString(); groupData["updated_by"] = userId; } else { apiUrlData = '$apiUrl/api/groups/create'; groupData["created_by"] = userId; } final token = await getToken(); // Fetch token if (token == null) { throw Exception('Token not found. Please log in.'); } try { final uri = Uri.parse(apiUrlData); final headers = { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'app-signature': 'ts-traveltool-2025-signature-123456', }; final body = jsonEncode(groupData); final response = groupDataId != null ? await http.put(uri, headers: headers, body: body) : await http.post(uri, headers: headers, body: body); if (response.statusCode == 200 || response.statusCode == 201) { print("Group Details Created successfully!"); print("Response: ${response.body}"); // _clearError(); _clearError(); await apiService.handleTokenRefresh(context, userId!); await widget.fetchGetGroup(); if (context.mounted) { Navigator.of(context).pop(); // Close modal only if mounted } setState(() { isDisable = false; }); // dispose(); } else if (response.statusCode == 404) { if (context.mounted) { Navigator.of(context).pop(); } setState(() { isDisable = false; }); final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: Colors.redAccent, behavior: SnackBarBehavior.floating, ), ); } else if (response.statusCode == 403) { print("403-FORB"); await apiService.logout(context); return null; // throw Exception('Failed to load users'); } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); final message = jsonDecode(response.body); final errorMessage = message['messages']?['error'] ?? 'Unknown error occurred'; if (errorMessage.contains("Duplicate entry")) { _clearError(); await widget.fetchGetGroup(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text("Group Already Exists!"), backgroundColor: Colors.orange, behavior: SnackBarBehavior.floating, ), ); } setState(() { isDisable = false; }); } } catch (e) { print(" Error submitting plan: $e"); setState(() { isDisable = false; }); } } @override Widget build(BuildContext context) { late Map DomesticMap; late List DomesticID; late Map InternationalMap; late List InternationalID; domesticList = apiForDomestic ?? []; internationalList = apiForInternational ?? []; // print("domesticListFromAPI--$domesticList"); // Map id to names DomesticMap = { for (var object in domesticList) object['policy_id'] as String: object['name'] as String, }; // print("domestic -- map--$DomesticMap"); // Extract only id for processing DomesticID = DomesticMap.keys.toList(); // print("domestic -- id--$DomesticID"); // print("domestic -- selected --$selectedDomesticPolicyID"); InternationalMap = { for (var item in internationalList) item['policy_id'] as String: item['name'] as String, }; // Extract only id for processing InternationalID = InternationalMap.keys.toList(); selectedInternationalPolicyID ??= null; return AlertDialog( backgroundColor: Colors.white, // contentPadding: const EdgeInsets.fromLTRB(44, 40, 44, 40), contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), // contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), content: Column( mainAxisSize: MainAxisSize.min, children: [ // Row 1: Title + Edit + Delete buttons Row( children: [ Text( (groupDataId != null) ? 'Edit Group' : 'Create Group', style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), ), const Spacer(), ], ), const SizedBox(height: 2), Divider(thickness: 0.2, color: Colors.blueGrey.shade100), const SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Name *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( // isFocused: false, isFocused: focusStates["nameFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["nameFocusNode"], inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'[a-zA-Z0-9 _-]'), ), ], controller: controllers["name"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Name", labelStyle: TextStyle(fontSize: 11, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["name"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["name"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Text( // "Select Policy For Domestic", // style: GoogleFonts.poppins( // fontSize: 12, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74), // ), // ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Select Policy For Domestic", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), IconButton( icon: Icon( Icons.remove_circle_sharp, size: 12, color: Colors.redAccent, ), tooltip: "Reset", onPressed: () { setState(() { selectedDomesticPolicyID = null; selectedDomesticPolicyName = null; }); }, ), ], ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["domestic_policy_nameFocused"] ?? false, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: Focus( focusNode: focusNodes["domestic_policy_nameFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["domestic_policy_nameFocused"] = hasFocus; }); }, child: GestureDetector( // onTap: () { // Request focus when user taps focusNodes["domestic_policy_nameFocusNode"] ?.requestFocus(); }, child: DropdownSearch( selectedItem: DomesticMap[selectedDomesticPolicyID], popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality menuProps: const MenuProps( backgroundColor: Colors.white, ), constraints: BoxConstraints(maxHeight: 250), itemBuilder: (context, object, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0, ), child: Text( object, style: GoogleFonts.poppins(fontSize: 11.5), ), ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search ...", hintStyle: GoogleFonts.poppins(fontSize: 11), contentPadding: EdgeInsets.symmetric( horizontal: 4, ), ), ), ), items: DomesticMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["domestic_policy_nameFocused"] ?? false) ? widget.layoutColor! : Colors.white, // width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["domestic_policy_nameFocused"] ?? false) ? widget.layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), // width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: widget.layoutColor!, width: 1, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 10.0, vertical: 8.0, ), // contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select ", style: GoogleFonts.poppins(fontSize: 11), ), ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name selectedDomesticPolicyID = DomesticMap.entries .firstWhere( (entry) => entry.value == newValue, ) .key; selectedDomesticPolicyName = newValue; }); }, ), ), ), ), ), ], ), const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Select Policy For International", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), IconButton( icon: Icon( Icons.remove_circle_sharp, size: 12, color: Colors.redAccent, ), tooltip: "Reset", onPressed: () { setState(() { selectedInternationalPolicyID = null; selectedInternationalPolicyName = null; }); }, ), ], ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["international_policy_nameFocused"] ?? false, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: Focus( focusNode: focusNodes["international_policy_nameFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["international_policy_nameFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["international_policy_nameFocusNode"] ?.requestFocus(); }, child: DropdownSearch( selectedItem: InternationalMap[selectedInternationalPolicyID], popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality menuProps: const MenuProps( backgroundColor: Colors.white, ), constraints: BoxConstraints(maxHeight: 250), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0, ), child: Text( item, style: GoogleFonts.poppins(fontSize: 11.5), ), ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search ...", hintStyle: GoogleFonts.poppins(fontSize: 11), contentPadding: EdgeInsets.symmetric( horizontal: 4, ), ), ), ), items: InternationalMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["international_policy_nameFocused"] ?? false) ? widget.layoutColor! : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["international_policy_nameFocused"] ?? false) ? widget.layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: widget.layoutColor!, width: 1, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 10.0, vertical: 8.0, ), // contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: GoogleFonts.poppins(fontSize: 11), ), ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name selectedInternationalPolicyID = InternationalMap.entries .firstWhere( (entry) => entry.value == newValue, ) .key; selectedInternationalPolicyName = newValue; }); }, ), ), ), ), ), ], ), const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Description *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["descriptionFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 100, child: TextField( controller: controllers["description"], focusNode: focusNodes["descriptionFocusNode"], inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'[a-zA-Z0-9 _-]'), ), ], style: const TextStyle(fontSize: 12), maxLines: null, expands: true, decoration: const InputDecoration( labelText: "Description", labelStyle: TextStyle(fontSize: 11, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["description"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["description"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), SizedBox(height: 15), if (groupDataId != null) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Change Status ", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), Tooltip( message: isActive == "1" ? "Tap to deactivate" : "Tap to activate", child: GestureDetector( onTap: toggleStatus, child: Text( isActive == "1" ? "Active" : "Inactive", style: TextStyle( fontSize: 13, fontFamily: "Inter", color: isActive == "1" ? Colors.green : Colors.red, ), ), ), ), ], ), if (groupDataId != null) SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ // SizedBox( // child: ElevatedButton( // onPressed: () { // // You can get text from commentController.text // Navigator.of(context).pop(); // Close the modal // }, // style: ElevatedButton.styleFrom( // backgroundColor: widget.layoutColor, // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8), // ), // ), // child: Text('Cancel', // style: GoogleFonts.poppins( // fontSize: 13, color: Colors.white)), // ), // ), // SizedBox( // width: 10, // ), SizedBox( child: ElevatedButton( // onPressed: () { // handleSubmit(); // // You can get text from commentController.text // // Navigator.of(context).pop(); // Close the modal // }, onPressed: isDisable ? null : () async { setState(() { isDisable = true; }); await handleSubmit(); // You can get text from commentController.text // Navigator.of(context).pop(); // Close the modal // Optional: re-enable only on error // setState(() { // isDisable = false; // }); }, style: ElevatedButton.styleFrom( backgroundColor: widget.layoutColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( 'Save', style: GoogleFonts.poppins( fontSize: 11, color: Colors.white, ), ), ), ), ], ), // : SizedBox.shrink(), ], ), ); } }