ts-tat/lib/Screens/group/groupDetails.dart
venba-Inspriron-3558 35229fa755 july 1 to july 15
2025-07-15 15:58:38 +05:30

874 lines
31 KiB
Dart

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, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
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";
bool isDisable = false;
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();
// }
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<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(() {
isDisable = true;
// This triggers UI rebuild with error messages
if (validateData()) {
postGroupData();
}else{
isDisable = false;
}
});
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();
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 {
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<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,
isFocused: focusStates["nameFocused"] ?? false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
focusNode: focusNodes["nameFocusNode"],
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<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: "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<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: "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"],
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(),
],
),
);
}
}