group changes

This commit is contained in:
VE10-Sanjeev 2025-05-28 06:33:30 +00:00
parent b630ac35b7
commit 2891e93d8d
4 changed files with 775 additions and 4 deletions

View File

@ -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<void> Function() fetchGetGroup;
final bool isDesktop;
final Color? layoutColor;
final int? groupId; // <-- Add this
final Map<String, dynamic>? 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<GroupData> {
final ApiService apiService = ApiService();
Map<String, String> DomesticMap = {};
Map<String, String> InternationalMap = {};
late List<dynamic>? apiForDomestic;
late List<dynamic>? apiForInternational;
Map<String, dynamic>? apiData;
final Map<String, TextEditingController> controllers = {};
Map<String, String> errorMessages = {};
List<dynamic> domesticList = [];
List<dynamic> internationalList = [];
String? selectedName;
String? selectedDescription;
String? selectedInternationalPolicyID;
String? selectedInternationalPolicyName;
String? selectedDomesticPolicyID;
String? selectedDomesticPolicyName;
String? userId;
int? groupDataId;
late String isActive = "1";
List<String> dataHeader = [
"name",
"description",
"domestic_policy_id",
"domestic_policy_name",
"international_policy_id",
"international_policy_name",
];
Map<String, dynamic> 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<void> 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<void> handleSubmit() async {
userId = await getUserId();
setState(() {
// This triggers UI rebuild with error messages
if (validateData()) {
postGroupData();
}
});
final groupData1 = group_Detials();
print("GroupDAta - $groupData1");
}
Future<void> 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<String, String> DomesticMap;
late List<String> DomesticID;
late Map<String, String> InternationalMap;
late List<String> 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<String>(
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<String>(
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(),
],
),
);
}
}

View File

@ -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<GroupList> {
handleActiveStatus(groupdata, groupId.toString(), status.toString());
}
Future<void> refreshData() async {
loadAllGroups();
}
// Future<void> deleteGroupFromApi(int groupId) async {
// try {
// await apiService.deleteGroup(groupId); // your delete API call
@ -236,7 +241,18 @@ class _GroupListState extends State<GroupList> {
),
onPressed: () async {
// List<dynamic> 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<GroupList> {
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),
),

View File

@ -96,7 +96,7 @@ class OrganizationSettingState extends State<OrganizationSetting> {
},
{
'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<OrganizationSetting> {
case '/templateList':
case '/costcenter':
case '/hotels':
case '/groupDetails' :
context.go(route);
break;
default:

View File

@ -1048,4 +1048,87 @@ class ApiService {
throw Exception('Failed to load CostCenter details');
}
}
Future<Map<String, dynamic>> 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<Map<String, dynamic>> listData =
List<Map<String, dynamic>>.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<Map<String, dynamic>> 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<String, dynamic> 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');
}
}
}