import 'dart:convert'; import 'dart:core'; import 'dart:io'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:frontend/Screens/organization/mailSettings.dart'; import 'package:frontend/Screens/organization/themeColor.dart'; import 'package:frontend/utils/auth_utils.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; import 'package:responsive_builder/responsive_builder.dart'; import '../../config/apiUrl.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_user_form.dart'; import 'groupList.dart'; class Group extends StatefulWidget { final Map? group; const Group({Key? key, required this.group}) : super(key: key); static Group fromState(GoRouterState state) { return Group(group: state.extra as Map?); } @override _groupState createState() => _groupState(); } class _groupState extends State { final ApiService apiService = ApiService(); Color? layoutColor; Color? bodyColor; String? orgId; String? userId; String? selectedGroupId; String? selectedDomestic; String? selectedInternational; List? apiAllPolicy; List? apiForDomestic; List? apiForInternational; Map errorMessages = {}; final Map controllers = {}; Map focusNodes = {}; Map focusStates = {}; List dataHeader = ["name", "description"]; Map get groupData { final data = { "name": controllers["name"]?.text, "description": controllers["description"]?.text, "domestic_policy_id": selectedDomestic, "international_policy_id": selectedInternational, "org_id": orgId, "created_by": userId, }; // Only add group_id if it's an edit operation if (widget.group != null && widget.group!.containsKey('group_id')) { data["group_id"] = selectedGroupId; } return data; } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { loadAllServices(); 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()}"); updateData(); loadInitialData(); }); } void loadInitialData() async { String? layoutString = await getLayoutColor(); String? bodyStringColor = await getBodyColor(); setState(() { layoutColor = layoutString != null ? Color(int.parse(layoutString)) : Colors.redAccent; bodyColor = bodyStringColor != null ? Color(int.parse(bodyStringColor)) : Colors.white; }); } Future updateData() async { // Ensure apiselectedUser is not null before printing if (widget.group != null) { print("API Selected User Has Data - ${widget.group}"); setState(() { // ✅ Wrap in setState to update the UI selectedGroupId = widget.group?["group_id"] ?? ""; controllers["name"]?.text = widget.group?["name"] ?? ""; controllers["description"]?.text = widget.group?["description"] ?? ""; if (widget.group?["domestic_policy_id"] != null) { selectedDomestic = widget.group!["domestic_policy_id"].toString(); } if (widget.group?["international_policy_id"] != null) { selectedInternational = widget.group!["international_policy_id"].toString(); } }); } else { print("API Selected User Has Data - No data available yet"); } } Future loadAllServices() async { try { final result = await apiService.fetchAllPolicy(context); orgId = await getOrgId(); userId = await getUserId(); final roleUser = await getRoleUser(); if (roleUser != null && (roleUser == 'Org Admin' || roleUser == 'Travel Admin')) { setState(() { apiAllPolicy = result; apiForDomestic = result.where((policy) => policy["domestic"] == "1").toList(); apiForInternational = result.where((policy) => policy["international"] == "1").toList(); }); print("Fetched services: $apiAllPolicy"); print("Domestic policies: $apiForDomestic"); print("International policies: $apiForInternational"); } else { apiService.logout(context); } } catch (e) { print('Error fetching role list: $e'); } } bool isValidData(Map data) { errorMessages.clear(); // Reset errors // Required fields that must not be empty List 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"; } } // ✅ At least one of the two policies must be selected if ((selectedDomestic == null || selectedDomestic!.isEmpty) && (selectedInternational == null || selectedInternational!.isEmpty)) { errorMessages["domestic_policy_id"] = "Select at least one policy"; errorMessages["international_policy_id"] = "Select at least one policy"; } return errorMessages.isEmpty; return errorMessages.isEmpty; // Valid if there are no errors } void _clearError(String field) { if (mounted && errorMessages.containsKey(field)) { setState(() { errorMessages.remove(field); }); } } void handleSubmit() { print("HandleSubmiy - $groupData"); setState(() { if (!isValidData(groupData)) { print("USERDETAILS : $groupData"); print("Validation Failed: Required fields are missing."); return; // Stop execution if validation fails } else { print("USERDETAILS : $groupData"); postGroupData(groupData); } }); } Future postGroupData(Map groupData) async { final token = await getToken(); // Fetch token final bool isEdit = widget.group != null && widget.group!.isNotEmpty; print("isEdit: $isEdit"); print("postGroupData- ${widget.group?['group_id']}"); String apiUrlData; if (isEdit) { if (selectedGroupId == null) { print("selectedGroupId is null. Cannot proceed."); return; } final int? groupId = int.tryParse(selectedGroupId!); if (groupId == null) { print("groupId is invalid. selectedGroupId = $selectedGroupId"); return; } apiUrlData = '$apiUrl/api/groups/update/$groupId'; } else { apiUrlData = '$apiUrl/api/groups/create'; } if (token == null) { throw Exception('Token not found. Please log in.'); } try { final response = await (isEdit ? http.put( Uri.parse(apiUrlData), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'app-signature': 'ts-traveltool-2025-signature-123456', }, body: jsonEncode(groupData), ) : http.post( Uri.parse(apiUrlData), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'app-signature': 'ts-traveltool-2025-signature-123456', }, body: jsonEncode(groupData), )); if (response.statusCode == 200 || response.statusCode == 201) { print("GRPDATA"); print("Group submitted successfully!"); print("Response: ${response.body}"); await apiService.handleTokenRefresh(context, userId!); context.go('/group'); } 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 group. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print("Error submitting group: $e"); } } @override Widget build(BuildContext context) { return ResponsiveBuilder( builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( // backgroundColor: Colors.white, backgroundColor: Color(0xFFf5f5f5), appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.1, // 30% of screen width as horizontal padding vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) : EdgeInsets.all(8), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), Expanded(child: buildData(isDesktop, context)), ], ), ), ); }, ); } Widget buildData(bool isDesktop, context) { return Container( margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: isDesktop ? Colors.white : Color(0xFFFCFCFC), ), child: Column( children: [ Expanded( child: Container( // color: bodyColor, // color: Color(0xFFE1F5FE), child: buildOrganizationLayout(isDesktop), ), ), Container( color: Colors.white, padding: const EdgeInsets.all(8.0), child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.end, children: _buildSubmit(isDesktop), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: _buildSubmit(isDesktop), ), ), ], ), ); } Widget buildOrganizationLayout(isDesktop) { return Container( // margin: isDesktop // ? EdgeInsets.all(10.0) // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), height: isDesktop ? MediaQuery.of(context).size.height * 0.98 : MediaQuery.of(context).size.height, // decoration: BoxDecoration( // border: isDesktop // ? Border.all( // width: 2, // color: Colors.white, // // color: Color(0xFFF7F7FB), // ) // : null, // color: Colors.white, // // // color: Color(0xFFF7F7FB), // // // color: Colors.amber, // ), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( // color: Colors.white, padding: const EdgeInsets.all(10), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Create Group", style: GoogleFonts.poppins(fontSize: 18)), // Container( // color: Color(0xFFE9EBF6), // child: IconButton( // icon: Icon( // Icons.close, // ), // onPressed: () { // context.go('/group'); // }, // ), // ) ], ), ), // SizedBox( // height: 5, // ), Container( margin: const EdgeInsets.all(10), padding: const EdgeInsets.all(20), color: Colors.white, height: MediaQuery.of(context).size.height * 0.6, child: Column( children: [ // Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // Text( // "Mail Settings", // style: TextStyle(color: Colors.blueAccent), // ), // Icon( // Icons.keyboard_arrow_down_outlined, // color: Colors.blueAccent, // size: 30, // ), // ], // ), isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: _buildFirstRow(isDesktop), ) : Column( crossAxisAlignment: CrossAxisAlignment.center, children: _buildFirstRow(isDesktop), ), SizedBox(height: 10), isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: _buildSecondRow(isDesktop), ) : Column( crossAxisAlignment: CrossAxisAlignment.center, children: _buildSecondRow(isDesktop), ), // SizedBox(height: 15), ], ), ), ], ), ); } List _buildFirstRow(bool isDesktop) { return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Group Name", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 40, child: TextField( controller: controllers["name"], inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')), ], style: TextStyle(fontSize: 12), onChanged: (value) { _clearError("name"); }, decoration: InputDecoration( labelText: "group name", labelStyle: TextStyle(fontSize: 12, 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), ), ], ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Description", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 40, child: TextField( controller: controllers["description"], inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')), ], onChanged: (value) { _clearError("description"); }, style: TextStyle(fontSize: 12), decoration: InputDecoration( labelText: "description", labelStyle: TextStyle(fontSize: 12, 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), ), ], ], ), ]; } List _buildSecondRow(bool isDesktop) { return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Select Policy For Domestic", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 40, child: apiForDomestic == null ? Center( child: Transform.scale( scale: 0.5, child: CircularProgressIndicator(), ), ) : DropdownSearch( selectedItem: selectedDomestic == null ? null : apiForDomestic! .firstWhere( (policy) => policy['policy_id'] == selectedDomestic, )['name'] .toString(), popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Policy...", contentPadding: EdgeInsets.symmetric( horizontal: 10, ), ), ), ), items: apiForDomestic! .map((policy) => policy['name'].toString()) .toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: TextStyle(fontSize: 12), ), ), onChanged: (String? newValue) { setState(() { selectedDomestic = apiForDomestic!.firstWhere( (policy) => policy['name'] == newValue, )['policy_id']; }); }, ), ), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Select Policy For International", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 40, child: apiForInternational == null ? Center( child: Transform.scale( scale: 0.5, child: CircularProgressIndicator(), ), ) : DropdownSearch( selectedItem: selectedInternational == null ? null : apiForInternational! .firstWhere( (policy) => policy['policy_id'] == selectedInternational, )['name'] .toString(), popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Policy...", contentPadding: EdgeInsets.symmetric( horizontal: 10, ), ), ), ), items: apiForInternational! .map((policy) => policy['name'].toString()) .toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: TextStyle(fontSize: 12), ), ), onChanged: (String? newValue) { setState(() { selectedInternational = apiForInternational!.firstWhere( (policy) => policy['name'] == newValue, )['policy_id']; }); }, ), ), ), ], ), ]; } List _buildSubmit(isDesktop) { return [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: layoutColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor ?? Colors.green, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { context.go('/group'); }, child: Text("Cancel"), ), SizedBox(width: 20), MouseRegion( // cursor: widget.isViewMode // ? SystemMouseCursors.forbidden // : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: layoutColor, // Keep original color foregroundColor: Colors.white, // Keep original color // disabledBackgroundColor: // Colors.blueAccent, // Ensure color remains when disabled // disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), // side: BorderSide(color: Colors.blueAccent, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: handleSubmit, // Disable when in view mode child: Text("Submit"), ), ), ]; } }