From 2891e93d8d03e363b65c8e752ab1d797d39147d6 Mon Sep 17 00:00:00 2001 From: VE10-Sanjeev Date: Wed, 28 May 2025 06:33:30 +0000 Subject: [PATCH] group changes --- lib/Screens/group/groupDetails.dart | 645 ++++++++++++++++++++++++++++ lib/Screens/group/groupList.dart | 48 ++- lib/routes/organizationSetting.dart | 3 +- lib/services/apiService.dart | 83 ++++ 4 files changed, 775 insertions(+), 4 deletions(-) create mode 100644 lib/Screens/group/groupDetails.dart diff --git a/lib/Screens/group/groupDetails.dart b/lib/Screens/group/groupDetails.dart new file mode 100644 index 0000000..f2ebb58 --- /dev/null +++ b/lib/Screens/group/groupDetails.dart @@ -0,0 +1,645 @@ +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 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"; + + 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(); + } + + if (widget.groupId != null) { + print('Editing group ID: ${widget.groupId}'); + updateGroupDetails(); + } + _clearError(); + } + + void _clearError() { + setState(() { + errorMessages.clear(); + }); + } + + @override + void dispose() { + for (var controller in controllers.values) { + controller.dispose(); + } + super.dispose(); + } + + 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(); + + userId = await getUserId(); + + 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"); + } 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(() { + // This triggers UI rebuild with error messages + if (validateData()) { + postGroupData(); + } + }); + + 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', + }; + 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 widget.fetchGetGroup(); + + // dispose(); + Navigator.of(context).pop(); + } else if (response.statusCode == 404) { + Navigator.of(context).pop(); + final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.redAccent, + behavior: SnackBarBehavior.floating, + ), + ); + } else { + print("Failed to submit plan. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + } catch (e) { + print(" Error submitting plan: $e"); + } + } + + @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, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + 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 International", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + 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: "Select Policy For International", + hintStyle: GoogleFonts.poppins(fontSize: 11), + contentPadding: EdgeInsets.symmetric(horizontal: 4), + ), + ), + ), + items: InternationalMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 1, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Policy For International", + 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( + "Select Policy For Domestic", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + 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: "Select Policy For Domestic...", + hintStyle: GoogleFonts.poppins(fontSize: 11), + contentPadding: EdgeInsets.symmetric(horizontal: 4), + ), + ), + ), + items: DomesticMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 1, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Policy For Domestic", + 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: [ + Text( + "Description", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 100, + child: TextField( + controller: controllers["description"], + 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 + }, + 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(), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart index 38c0875..e7591c0 100644 --- a/lib/Screens/group/groupList.dart +++ b/lib/Screens/group/groupList.dart @@ -12,6 +12,7 @@ import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; +import 'groupDetails.dart'; class GroupList extends StatefulWidget { @override @@ -117,6 +118,10 @@ class _GroupListState extends State { handleActiveStatus(groupdata, groupId.toString(), status.toString()); } + Future refreshData() async { + loadAllGroups(); + } + // Future deleteGroupFromApi(int groupId) async { // try { // await apiService.deleteGroup(groupId); // your delete API call @@ -236,7 +241,18 @@ class _GroupListState extends State { ), onPressed: () async { // List users = await futureUsers; - context.go('/CreateGroup'); + // context.go('/CreateGroup'); + showDialog( + context: context, + builder: (context) => GroupData( + isDesktop: isDesktop, + groupId: null, + layoutColor: layoutColor!, + fetchGetGroup: refreshData + ), + ); + + }, child: Row( children: [ @@ -355,9 +371,35 @@ class _GroupListState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( - onTap: () { - context.go("/CreateGroup", extra: group); + onTap: () async { + + if (group['group_id'] != null) { + final newGroupID = int.tryParse(group['group_id'].toString()); + if (newGroupID != null) { + final data = await apiService.getGroupDetailsFind(newGroupID); // ✅ Always an int + showDialog( + context: context, + builder: (context) => GroupData( + isDesktop: isDesktop, + groupId: newGroupID, // Pass the ID + groupData: data, + layoutColor: layoutColor!, + fetchGetGroup: refreshData + ), + ); + + + + } + + } else { + print("something went wrong check properly"); + } }, + + // onTap: () { + // context.go("/CreateGroup", extra: group); + // }, child: Image.asset('assets/images/IconsImg/edit.png', width: 20, height: 15), ), diff --git a/lib/routes/organizationSetting.dart b/lib/routes/organizationSetting.dart index 35ea714..bc50865 100644 --- a/lib/routes/organizationSetting.dart +++ b/lib/routes/organizationSetting.dart @@ -96,7 +96,7 @@ class OrganizationSettingState extends State { }, { 'value': '/hotels', - 'icon': Icons.cabin_sharp, + 'icon': Icons.add_business, 'label': 'Hotels', 'description': 'Create and Edit Hotels' }, @@ -126,6 +126,7 @@ class OrganizationSettingState extends State { case '/templateList': case '/costcenter': case '/hotels': + case '/groupDetails' : context.go(route); break; default: diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index bb0c979..3cab162 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -1048,4 +1048,87 @@ class ApiService { throw Exception('Failed to load CostCenter details'); } } + Future> getHotelsDetailsFind(int id) async { + final String apiUrldata = '$apiUrl/api/findHotels?hotel_id=$id'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + // print('findout the result'); + // print(data.runtimeType); + // print(data); + + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception( + "Invalid response format: 'data' field is missing or not a List"); + } + + final List> listData = + List>.from(data['data']); + + if (listData.isEmpty) { + throw Exception("No Hotel data found with ID $id"); + } + + return listData[0]; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load Hotel details'); + } + } + Future> getGroupDetailsFind(int id) async { + final String apiUrldata = '$apiUrl/api/groups/find/$id'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print(data); + if (!data.containsKey('data') || data['data'] is! Map) { + throw Exception( + "Invalid response format: 'data' field is missing or not a Map"); + } + print('Single USer 1'); + + Map plansJson = + data['data']; // 'data' is a Map, not a List + + print('Single USer 2'); + return plansJson; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } }