import 'dart:convert'; 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 'travellerList.dart'; class TravellerData extends StatefulWidget { final Future> Function() fetchGetTraveller; final bool isDesktop; final Color? layoutColor; final int? travellerId; // <-- Add this final Map? travellerData; const TravellerData({ super.key, required this.isDesktop, this.layoutColor, required this.fetchGetTraveller, this.travellerId, this.travellerData, }); @override TravellerDataState createState() => TravellerDataState(); } class TravellerDataState extends State { final ApiService apiService = ApiService(); Map? apiData; // final Map focusNodes = { // "name": FocusNode(), // "description": FocusNode(), // }; Map focusNodes = {}; Map focusStates = {}; final Map controllers = {}; Map errorMessages = {}; String? selectedName; String? selectedDescription; String? userId; int? travellerDataId; late String isActive = "1"; bool isDisable = false; List dataHeader = ["first_name", "last_name", "email", "mobile"]; Map travellerDetails() { final data = { // "traveller_id": int.parse(travellerId), "first_name": controllers["first_name"]?.text, "last_name": controllers["last_name"]?.text, "email": controllers["email"]?.text, "mobile": controllers["mobile"]?.text, "is_active": isActive, }; return data; } @override void initState() { super.initState(); apiData = null; for (var field in dataHeader) { controllers[field] = TextEditingController(); focusNodes["${field}FocusNode"] = FocusNode(); focusStates["${field}Focused"] = false; } for (var key in focusNodes.keys) { _addFocusListener(focusNodes[key]!, (focus) { setState(() { focusStates[key.replaceFirst("FocusNode", "Focused")] = focus; }); }); } if (widget.travellerId != null) { print('Editing D ID: ${widget.travellerId}'); updateTravellerDetails(); } } void _clearError() { setState(() { errorMessages.clear(); }); } @override void dispose() { for (var controller in controllers.values) { controller.dispose(); } for (var node in focusNodes.values) { node.dispose(); } super.dispose(); } void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { updateState(node.hasFocus); }); }); } void updateTravellerDetails() { print("Inside Update Function - ${widget.travellerData}"); final data = widget.travellerData; if (data == null) return; setState(() { controllers['first_name']?.text = data['first_name'] ?? ''; controllers['last_name']?.text = data['last_name'] ?? ''; controllers['email']?.text = data['email'].toString(); controllers['mobile']?.text = data['mobile'].toString(); isActive = data["is_active"]; final travellerId = int.tryParse(data['traveller_id'].toString()); travellerDataId = travellerId; }); } void toggleStatus() { setState(() { isActive = isActive == "1" ? "0" : "1"; }); } bool validateData() { errorMessages.clear(); final data = { "first_name": controllers["first_name"]?.text, "last_name": controllers["last_name"]?.text, "email": controllers["email"]?.text, "mobile": controllers["mobile"]?.text, }; final requiredFields = ["first_name", "last_name", "email", "mobile"]; bool hasFocused = false; // Check validation for each field for (String field in requiredFields) { if (data[field] == null || data[field]!.trim().isEmpty) { errorMessages[field] = "Required"; // if (!hasFocused) { // focusNodes[field]?.requestFocus(); // hasFocused = true; // } } } if (data["mobile"] != null && data["mobile"].toString().isNotEmpty) { if (!RegExp(r"^\d{10}$").hasMatch(data["mobile"].toString())) { errorMessages["mobile"] = "Enter 10 digits"; // Invalid mobile number format } } if (data["email"] != null && data["email"].toString().isNotEmpty) { if (!RegExp( r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", ).hasMatch(data["email"].toString())) { errorMessages["email"] = "Invalid email format"; // Invalid email format } } return errorMessages.isEmpty; } Future handleSubmit() async { userId = await getUserId(); setState(() { isDisable = true; // This triggers UI rebuild with error messages if (validateData()) { postTravellerData(); } else { isDisable = false; } }); final travellerData1 = travellerDetails(); print("submit data - $travellerData1"); } Future postTravellerData({int isActive = 1}) async { // final remarksData = getData(); final travellerData = travellerDetails(); print("initially value of the Traveller - $travellerData"); // static here final orgId = await getOrgId(); final String apiUrldata; travellerData["org_id"] = orgId; if (travellerDataId != null) { print("for edit traveller id - $travellerDataId"); apiUrldata = '$apiUrl/api/travellers/update/$travellerDataId'; travellerData["traveller_id"] = travellerDataId.toString(); travellerData["updated_by"] = userId; (travellerData.containsKey("created_by")) ? travellerData.remove("created_by") : ''; } else { print("for add Traveller id - null"); apiUrldata = '$apiUrl/api/travellers/create'; print("called apiUrl - $apiUrldata"); travellerData["created_by"] = userId; } print("recently Traveller data - $travellerData"); 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(travellerData); final response = travellerDataId != null ? await http.put(uri, headers: headers, body: body) : await http.post(uri, headers: headers, body: body); switch (response.statusCode) { case 200: print("Update - Response: ${response.body}"); _clearError(); widget.fetchGetTraveller(); if (context.mounted) { Navigator.of(context).pop(); // Close modal only if mounted } setState(() { isDisable = false; }); break; case 201: print("Save - Response: ${response.body}"); _clearError(); await widget.fetchGetTraveller(); if (context.mounted) { Navigator.of(context).pop(); // Close modal only if mounted } setState(() { isDisable = false; }); break; case 403: print("403-FORB"); await apiService.logout(context); break; // throw Exception('Failed to load users'); default: print("Failed to submit traveller. Status: ${response.statusCode}"); print("Error: ${response.body}"); setState(() { isDisable = false; }); } } catch (e) { print(" Error submitting plan: $e"); setState(() { isDisable = false; }); } } @override Widget build(BuildContext context) { return AlertDialog( backgroundColor: Colors.white, contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), // contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), content: SizedBox( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ // Row 1: Title + Edit + Delete buttons Row( children: [ Text( (travellerDataId != null) ? 'Edit Traveller' : 'Create Traveller', 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( "First Name *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["first_nameFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'[a-zA-Z0-9 _-]'), ), ], controller: controllers["first_name"], focusNode: focusNodes["first_nameFocusNode"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "First Name", labelStyle: TextStyle( fontSize: 11, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["first_name"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["first_name"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Last Name *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["last_nameFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'[a-zA-Z0-9 _-]'), ), ], controller: controllers["last_name"], focusNode: focusNodes["last_nameFocusNode"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Last Name", labelStyle: TextStyle( fontSize: 11, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["last_name"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["last_name"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Email *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["emailFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( controller: controllers["email"], focusNode: focusNodes["emailFocusNode"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Email", labelStyle: TextStyle( fontSize: 11, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["email"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["email"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Mobile *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["mobileFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( controller: controllers["mobile"], focusNode: focusNodes["mobileFocusNode"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Mobile", labelStyle: TextStyle( fontSize: 11, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["mobile"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["mobile"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), SizedBox(height: 15), if (travellerDataId != 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 (travellerDataId != 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: 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(), ], ), ), ), ); } }