import 'dart:convert'; import 'dart:async'; import 'dart:typed_data'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:web/web.dart' as web; import 'package:flutter/material.dart'; import 'package:frontend/Screens/plans/dynamic_itinerary_stepper.dart'; import 'package:frontend/utils/auth_utils.dart'; import 'package:go_router/go_router.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'package:universal_html/html.dart' as html; import '../../config/apiUrl.dart'; import '../../data/models/plan.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../widgets/custom_radio_button.dart'; import '../../widgets/custom_text_field.dart'; import '../approvals/approval_dialogs.dart'; import '../dialog/user_selection_dialog.dart'; class CreatePlan extends StatefulWidget { CreatePlan({super.key}); @override _CreatePlansState createState() => _CreatePlansState(); } class _CreatePlansState extends State { final GlobalKey _createPlanKey = GlobalKey(); Color layoutColor = Colors.redAccent; Color bodyColor = Colors.white; @override void initState() { super.initState(); 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; }); } @override Widget build(BuildContext context) { return ResponsiveBuilder(builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( backgroundColor: Colors.white, appBar: const CustomAppBar(), 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: buildUserTable( isDesktop, context, bodyColor, layoutColor)), ], ), ), ); }); } Widget buildUserTable( bool isDesktop, context, Color? bodyColor, Color layoutColor) { final args = GoRouterState.of(context).extra as Map? ?? {}; // final planData = args?['planData']; final bool isViewMode = args?['isViewMode'] ?? false; final bool isApprover = args?['isApprover'] ?? false; final Map planData = args['planData'] as Map? ?? {}; // print("isViewMode: $isViewMode"); // final bool isViewMode = true; // final planData = GoRouterState.of(context).extra as Map? ?? {}; print("RECived palndata"); // print("RECived palndata - ${planData}"); return Container( // margin: const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0, bottom: 10.0), // decoration: BoxDecoration( // // color: Colors.amber, // // color: bodyColor, // color: Color(0xFFE1F5FE), // border: Border.all( // color: Colors.white, // // color: Color(0xFFF7F7FB), // // width: 3.5)), child: Column( children: [ // Container( // color: Color(0xFFF4F4FB), // padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16), // child: Row(children: [ // Row( // children: [ // Padding( // padding: const EdgeInsets.all(8.0), // child: Icon( // Icons.create_new_folder_outlined, // color: Color(0xFF84869A), // size: 23, // ), // ), // Text( // isViewMode // ? "View Plan" // : (planData.isNotEmpty ? "Update Plan" : "New Plan"), // style: TextStyle(fontSize: 18), // ), // ], // ), // Spacer(), // ]), // ), Expanded( child: Container( // margin: isDesktop // ? EdgeInsets.all(10.0) // : EdgeInsets.only( // left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // padding: const EdgeInsets.all(10), 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, // ), // color: bodyColor, child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(10.0), child: CreateNewPlan( key: _createPlanKey, bodyColor: bodyColor, layoutColor: layoutColor, isDesktop: isDesktop, selectedPlanData: planData, isViewMode: isViewMode, isApprover: isApprover, ), ), ), ), ), Container( padding: const EdgeInsets.all(10), color: Colors.white, child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.end, // children: [Text("Button")], children: _buildSubmit( isDesktop, isViewMode, layoutColor, isApprover), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: _buildSubmit( isDesktop, isViewMode, layoutColor, isApprover), )) ], ), ); } List _buildSubmit( isDesktop, bool isViewMode, Color layoutColor, bool isApprover) { return [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: layoutColor ?? Colors.blueAccent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { isApprover ? context.go('/approvallist') : context.go('/listPlan'); }, child: Text("Cancel")), SizedBox( width: 20, ), MouseRegion( cursor: isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isViewMode ? layoutColor : layoutColor, // Keep original color foregroundColor: isViewMode ? Colors.white : Colors.white, // Keep original color disabledBackgroundColor: layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: isViewMode ? null : () { _createPlanKey.currentState?.handleSubmit(); }, // Disable when in view mode child: Text("Submit"), ), ) ]; } } class CreateNewPlan extends StatefulWidget { final bool isDesktop; final bool isViewMode; final bool isApprover; final Color? bodyColor; final Color? layoutColor; final Map selectedPlanData; const CreateNewPlan({ super.key, required this.isDesktop, required this.bodyColor, required this.layoutColor, required this.selectedPlanData, required this.isViewMode, required this.isApprover, }); @override CreateNewPlansState createState() => CreateNewPlansState(); } class CreateNewPlansState extends State { final GlobalKey dynamicItineraryKey = GlobalKey(); final ApiService apiService = ApiService(); final TextEditingController _tripTitleController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); final TextEditingController _remarksController = TextEditingController(); final FocusNode _tripTitleFocusNode = FocusNode(); final FocusNode _descriptionFocusNode = FocusNode(); // Declare FocusNode bool _isTripTitleFocused = false; bool _isdescriptionFocused = false; late String _selectedOption = "Option 1"; // late String? _selectedIsBillable = "Billable"; bool isStatusExpanded = false; bool isShowApprovalAction = false; String? selectedPlanId; String? userDetails; String? userName; String? selfId; String? otherUserName; String? selectedplanUserId; bool? selectedIstravelUser; late Color layoutColorForUser; Map? apiData; // Store API response here Map? apiDataForClass; // Store API response here List? apiCountryData; List? apiCostData; // Store API response here bool isLoading = true; // Track loading state String? TripPlanAction; bool showDomestic = false; bool showInternational = false; bool hasAction = true; late Map costCenterMap; List costCenterIds = []; // List apiCostData = []; // if you’re not already using this late Map purposeMap; List purposeKeys = []; String? orgId; String? planUsrId; String? planTravlrId; String? statusValue; String? _selectedTripType; String? selectedCostCenterId; String? _selectedIsBillable; String? selectedFuncDept; String? selectedPurpose; Map validationErrors = {}; List> miscellaneousList = []; // List> miscellaneousList = [{"special_request": 1, "comments": "posta", "indx": 1}]; List> visaList = []; List> insuranceList = []; List> accommodationList = []; List> trainList = []; List> busList = []; List> taxiList = []; List> forexList = []; List> flightList = []; List> planStatusList = []; late bool isApproverApproved = false; late bool isApproverRejected = false; //Getter Method Map get planData => { "org_id": orgId, "user_id": planUsrId, "traveller_id": planTravlrId, "trip_title": _tripTitleController.text, "trip_type": _selectedTripType, "cost_center_id": selectedCostCenterId, "is_billable": _selectedIsBillable, "purpose_of_travel": selectedPurpose, "description": _descriptionController.text, "functional_department": selectedFuncDept, "so_number": "12345", "created_by": selfId, "updated_by": selfId, "is_active": "1", "flight": flightList, "accomodation": accommodationList, "bus": busList, "taxi": taxiList, "train": trainList, "visa": visaList, "forex": forexList, "insurance": insuranceList, "miscellaneous": miscellaneousList, // "status_value": statusValue, }; void handleItineraryUpdate(String type, List> newList) { setState(() { switch (type) { case "Miscellaneous": miscellaneousList = newList; break; case "Visa": visaList = newList; break; case "Insurance": insuranceList = newList; break; case "Train": trainList = newList; break; case "Bus": busList = newList; break; case "Taxi": taxiList = newList; break; case "Forex": forexList = newList; break; case "Flight": flightList = newList; break; case "Accomodation": accommodationList = newList; break; default: print("Unknown itinerary type: $type"); } }); print("Updated $type List: $newList"); } @override void initState() { super.initState(); fetchUserDetails(); fetchPlans(); fetchCostCenter(); fetchCountryList(); _tripTitleFocusNode.addListener(() { setState(() { _isTripTitleFocused = _tripTitleFocusNode.hasFocus; }); }); _descriptionFocusNode.addListener(() { setState(() { _isdescriptionFocused = _descriptionFocusNode.hasFocus; }); }); handleUpdateData(); } @override void dispose() { _tripTitleFocusNode.dispose(); _descriptionFocusNode.dispose(); super.dispose(); } void handleUpdateData() { if (widget.selectedPlanData != null) { setState(() { statusValue = widget.selectedPlanData['status_value'] ?? ''; print("STATUS____ : $statusValue"); planUsrId = widget.selectedPlanData['user_id'] ?? ''; _tripTitleController.text = widget.selectedPlanData['trip_title'] ?? ''; _descriptionController.text = widget.selectedPlanData['description'] ?? ''; _selectedTripType = widget.selectedPlanData['trip_type']; _selectedIsBillable = widget.selectedPlanData['is_billable'] == "1" ? "1" : "2"; // selectedCostCenterId = widget.selectedPlanData['cost_center_id']?.toString() ; // selectedPurpose = widget.selectedPlanData['purpose_of_travel']?.toString(); // selectedFuncDept =widget.selectedPlanData['functional_department']?.toString(); if (widget.selectedPlanData!["cost_center_id"] != null) { selectedCostCenterId = widget.selectedPlanData!["cost_center_id"].toString(); } if (widget.selectedPlanData['plan_status'] != null) { planStatusList = List>.from( widget.selectedPlanData['plan_status'] ?? []); } // if (widget.selectedPlanData!["purpose_of_travel"] != null) { selectedPurpose = widget.selectedPlanData!["purpose_of_travel"].toString(); } if (widget.selectedPlanData!["functional_department"] != null) { // selectedFuncDept = widget.selectedPlanData!["functional_department"].toString(); selectedFuncDept = widget.selectedPlanData!["functional_department"]; } // Assign lists from selectedPlanData, ensuring they are properly formatted flightList = List>.from( widget.selectedPlanData['flight'] ?? []); accommodationList = List>.from( widget.selectedPlanData['accomodation'] ?? []); busList = List>.from( widget.selectedPlanData['bus'] ?? []); taxiList = List>.from( widget.selectedPlanData['taxi'] ?? []); trainList = List>.from( widget.selectedPlanData['train'] ?? []); visaList = List>.from( widget.selectedPlanData['visa'] ?? []); forexList = List>.from( widget.selectedPlanData['forex'] ?? []); insuranceList = List>.from( widget.selectedPlanData['insurance'] ?? []); miscellaneousList = List>.from( widget.selectedPlanData['miscellaneous'] ?? []); }); if (widget.selectedPlanData['trip_type'] != null) { int? tripId = int.tryParse(widget.selectedPlanData['trip_type'].toString()); fetchTrainFlightClass(tripId!); } if (widget.selectedPlanData.containsKey('plan_id') && widget.selectedPlanData['plan_id'] != null) { print("Plan ID exists: ${widget.selectedPlanData['plan_id']}"); selectedPlanId = widget.selectedPlanData['plan_id']?.toString(); } else { print("Plan ID is missing or null"); } print("updatedPlanDAta - $planData"); } } void setTripPlanAction() { setState(() { if (TripPlanAction == "Plan Creation Not Allowed") { showDomestic = false; showInternational = false; hasAction = false; } else if (TripPlanAction == "Only Domestic Plan Creation Allowed") { showDomestic = true; showInternational = false; hasAction = true; } else if (TripPlanAction == "Only International Plan Creation Allowed") { showDomestic = false; showInternational = true; hasAction = true; } else if (TripPlanAction == "Both Type Plan Creation Allowed") { showDomestic = true; showInternational = true; hasAction = true; } }); } Future getPdfDownload() async { final String apiUrldata = '$apiUrl/api/plans/download?plan_id=$selectedPlanId'; // final String apiUrldata = '$apiUrl/auth/googlelogin'; 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 { print("PDf Dowloaded"); // Create a blob from the response body final blob = html.Blob([response.bodyBytes]); // Generate a download URL for the blob final url = html.Url.createObjectUrlFromBlob(blob); // Create a link element to trigger the download final anchor = html.AnchorElement(href: url) ..setAttribute('download', 'trip_plan_$selectedPlanId.pdf') ..click(); // Revoke the download URL to free up resources html.Url.revokeObjectUrl(url); } catch (e) { throw Exception('Error parsing response: $e'); } } else if (response.statusCode == 404) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('File not found.'), // content: Text('File not found.'), actions: [ TextButton( child: Text('OK'), onPressed: () { Navigator.of(context).pop(); // Close the dialog }, ), ], ); }, ); } else { throw Exception('Failed to load plans'); } } void getSelectedPlanFor() { if (!mounted) return; setState(() { if (selectedplanUserId != null) { if (selectedIstravelUser!) { planUsrId = ""; planTravlrId = selectedplanUserId; } else { planUsrId = selectedplanUserId; planTravlrId = ""; } } else { planUsrId = selfId; planTravlrId = ""; } }); print("USER ID - $planUsrId , TRAVELLER ID - $planTravlrId"); } void fetchUserDetails() async { final details = await getUserDetails(); TripPlanAction = await getTripPlanAction(); print("TripPlanAction- $TripPlanAction"); print("details- $details"); if (details != null) { setState(() { userDetails = details.toString(); // Store the full Map userName = details['name']; // Extract the name selfId = details['user_id']; }); } orgId = await getOrgId(); print("userDetails - $selfId"); getSelectedPlanFor(); setTripPlanAction(); } Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('auth_token'); } Future getUserId() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('userId'); } Future?> getUserDetails() async { final prefs = await SharedPreferences.getInstance(); final userData = prefs.getString('user_data'); if (userData != null) { final decodedData = jsonDecode(userData); return { 'user_id': decodedData['user_id'].toString(), 'name': "${decodedData['first_name']} ${decodedData['last_name']}", }; } return null; } Future fetchPlans() async { final String apiUrldata = '$apiUrl/api/getDropdownMaster'; 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"); } Map plansJson = data['data']; // 'data' is a Map, not a List setState(() { apiData = plansJson; // Store API response in state isLoading = false; }); } catch (e) { throw Exception('Error parsing response: $e'); } } else { throw Exception('Failed to load plans'); } } Future fetchCostCenter() async { final String apiUrldata = '$apiUrl/api/getCostCenterMaster'; final token = await getToken(); final userId = await getUserId(); // print("SUSRTRT- $userId"); // 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! List) { throw Exception( "Invalid response format: 'data' field is missing or not a List"); } List plansJson = data['data']; // 'data' is a Map, not a List // setState(() { // apiCostData = plansJson; // Store API response in state // // if(apiCostData!.isNotEmpty){ // // selectedCostCenterId =apiCostData?.first['department_id']; // // } // // if (apiCostData != null && apiCostData!.isNotEmpty) { // selectedCostCenterId ??= apiCostData!.first['name']?.toString(); // } // }); setState(() { apiCostData = plansJson; costCenterMap = { for (var item in apiCostData!) item['department_id'].toString(): item['name'].toString() }; costCenterIds = costCenterMap.keys.toList(); // Optionally auto-select the first item if not already selected selectedCostCenterId ??= costCenterIds.isNotEmpty ? costCenterIds.first : null; }); print('plansJSON'); } catch (e) { throw Exception('Error parsing response: $e'); } } else { throw Exception('Failed to load plans'); } } Future fetchCountryList() async { final String apiUrldata = '$apiUrl/api/getcountryMaster'; final token = await getToken(); final userId = await getUserId(); // print("SUSRTRT- $userId"); // 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("Country - $data"); if (!data.containsKey('data') || data['data'] is! List) { throw Exception( "Invalid response format: 'data' field is missing or not a List"); } List plansJson = data['data']; // 'data' is a Map, not a List if (data['data'] is List) { List plansJson = data['data']; print("plansJson.length - ${plansJson.length}"); } else { print("The 'data' key does not contain a list."); } setState(() { apiCountryData = plansJson; // Store API response in state }); print('plansJSONContry - $plansJson'); } catch (e) { throw Exception('Error parsing response: $e'); } } else { throw Exception('Failed to load plans'); } } Future fetchTrainFlightClass(int tripId) async { final userId = (planUsrId?.toString().isNotEmpty == true) ? planUsrId.toString() : (planTravlrId?.toString().isNotEmpty == true) ? planTravlrId.toString() : ''; // final String apiUrldata = '$apiUrl/api/getDropdownMaster'; final String apiUrldata = '$apiUrl/api/getFlightAndTrainClass?user_id=$userId&trip_type=$tripId'; 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"); } Map plansJson = data['data']; // 'data' is a Map, not a List setState(() { apiDataForClass = plansJson; // Store API response in state isLoading = false; }); } catch (e) { throw Exception('Error parsing response: $e'); } } else { throw Exception('Failed to load plans'); } } // Handle Submit bool validateForm() { validationErrors.clear(); // Clear previous errors // Ensure either "user_id" or "traveller_id" is provided if ((planUsrId == null || planUsrId!.isEmpty) && (planTravlrId == null || planTravlrId!.isEmpty)) { validationErrors["user_id"] = "Either User ID or Traveller ID is required"; validationErrors["traveller_id"] = "Either User ID or Traveller ID is required"; } final requiredFields = { if (TripPlanAction != "Plan Creation Not Allowed") // "trip_type": _selectedTripType, "cost_center_id": selectedCostCenterId, "functional_department": selectedFuncDept, "purpose_of_travel": selectedPurpose, }; for (var entry in requiredFields.entries) { if (entry.value == null || entry.value!.isEmpty) { validationErrors[entry.key] = "${entry.key.replaceAll('_', ' ').toUpperCase()} is required"; } } // Validate at least one service is selected final serviceLists = [ flightList, accommodationList, busList, taxiList, trainList, visaList, forexList, insuranceList, miscellaneousList, ]; bool anyServiceSelected = serviceLists.any((list) => list != null && list.isNotEmpty); if (!anyServiceSelected) { validationErrors["services"] = "Please select at least one service"; } return validationErrors.isEmpty; // Returns true if no errors } Future callApproveAPI(String planId, String userId) async { await postToAPI( endpoint: '/api/plans/approvePlan', data: { "plan_id": planId, "user_id": userId, }, methodName: 'Plan Approval', ); } Future callRejectAPI( String planId, String userId, String remarks) async { await postToAPI( endpoint: '/api/plans/rejectPlan', data: { "plan_id": planId, "user_id": userId, "reason": remarks, }, methodName: 'Plan Rejection', ); } Future postToAPI({ required String endpoint, required Map data, String methodName = '', }) async { final token = await getToken(); if (token == null) { throw Exception('Token not found. Please log in.'); } try { final response = await http.post( Uri.parse('$apiUrl$endpoint'), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode(data), ); if (response.statusCode == 200) { print("$methodName successful!"); print("Response: ${response.body}"); // await apiService.getViewPlan( // data['planId'],planData // ); print("ViewAAA - $planData"); print("Call viewPlanForApprover"); ApiService.viewPlanForApprover(context, data['plan_id'], isViewMode: false, isApprover: true); } else { print("$methodName failed. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print("Error in $methodName: $e"); } } void handleSubmit() { setState(() { if (validateForm()) { // if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { // planData['plan_id'] = selectedPlanId; // Add plan_id for update // } // // print("Form submitted successfully:" // " ${_remarksController.text}, ${planData['user_id']}, ${planData['traveller_id']}, " // "${planData['traveller_id']}," // " ${selectedPlanId}, " // " "); postPlanData(planData); widget.isApprover ? context.go('/approvallist') : context.go('/listPlan'); } }); } Future postPlanData(planData) async { final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan'; final token = await getToken(); // Fetch token if (token == null) { throw Exception('Token not found. Please log in.'); } if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { planData['plan_id'] = selectedPlanId; // Add plan_id for update } print("POSTPlanTesting------- $planData}"); try { final response = await http.post( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode(planData), // Convert map to JSON ); if (response.statusCode == 200) { print("Plan submitted successfully!"); print("Response: ${response.body}"); } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print(" Error submitting plan: $e"); } } Widget build(BuildContext context) { bool hasApprovals = planStatusList.any( (item) => item.entries.any( (entry) => entry.key.contains('status') && entry.value != null && entry.value.toString().isNotEmpty, ), ); return ResponsiveBuilder(builder: (context, sizingInfo) { bool isMobile = sizingInfo.isMobile; bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ResponsiveBuilder(builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Container( padding: const EdgeInsets.all(5), child: isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ..._buildApproverControls(isDesktop), Spacer(), Text( widget.isViewMode ? "View Plan" : (selectedPlanId != null && selectedPlanId!.isNotEmpty ? "Update Plan" : "New Plan"), style: TextStyle(fontSize: 18), ), Spacer(), ..._buildPlanPdf() ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ..._buildApproverControls(isDesktop), Text( widget.isViewMode ? "View Plan" : (planData.isNotEmpty ? "Update Plan" : "New Plan"), style: TextStyle(fontSize: 18), ), ..._buildPlanPdf() ], ), ); }), // if (isStatusExpanded) // Container( // margin: isDesktop // ? const EdgeInsets.only(left: 0, top: 0) // : const EdgeInsets.only(left: 5, top: 2), // padding: const EdgeInsets.all(12), // width: isDesktop // ? MediaQuery.of(context).size.width * 0.2 // : MediaQuery.of(context).size.width, // decoration: BoxDecoration( // color: Color(0xFFF5F5F5), // border: Border.all( // // color: Colors.grey.shade300, // color: Colors.white, // width: 0.2), // borderRadius: BorderRadius.circular(8), // // boxShadow: [ // // BoxShadow( // // // color: Colors.grey.withAlpha(20), // // color: Colors.grey.withAlpha(20), // // spreadRadius: 1.5, // // blurRadius: 7, // // offset: Offset(0, 4), // shadow direction: bottom // // ), // // ], // ), // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // if (!hasApprovals) // Center( // child: Text( // "--- No Approvals ---", // style: TextStyle( // fontFamily: "Archivo", // fontSize: 11, // fontWeight: FontWeight.w500, // color: Colors.black87, // ), // )), // for (int i = 0; i < planStatusList.length; i++) ...[ // if (planStatusList[i].entries.any((entry) => // entry.key.contains('status') && // entry.value != null && // entry.value.toString().isNotEmpty)) ...[ // _buildApprovalItem( // "Approver ${i + 1}", // planStatusList[i] // .entries // .firstWhere( // (entry) => entry.key.contains('status'), // orElse: () => MapEntry('', ''), // ) // .value // .toString(), // ), // SizedBox(height: 6), // ], // ], // ], // ), // ), if (isApproverRejected) Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(top: 4, left: 8), child: Text("Remarks : ", style: TextStyle( fontFamily: "Archivo", fontWeight: FontWeight.w600, fontSize: 11)), ), Expanded( child: Padding( padding: const EdgeInsets.only( top: 0), // tweak if needed child: TextFormField( controller: _remarksController, style: TextStyle( fontFamily: "Archivo", fontWeight: FontWeight.w600, fontSize: 11), decoration: const InputDecoration( hintText: "Please enter remarks...", hintStyle: TextStyle( color: Colors.grey, fontSize: 15), border: InputBorder.none, isDense: true, ), maxLines: null, ), ), ), ], ), // if (widget.isApprover || isStatusExpanded) Divider( thickness: 0.1, color: Colors.blueGrey, ), if (widget.isApprover) SizedBox( height: 5, ), isMobile ? SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: _buildPlanTrip(isMobile), ), ) : Row( children: _buildPlanTrip(isMobile), ), SizedBox(height: 13), Text.rich( TextSpan( text: "Planning this Trip For : ", // Static text style: TextStyle( fontFamily: "Archivo", fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF212121), // color: Color(0xFF575A74), // Default color ), children: [ TextSpan( text: otherUserName ?? userName ?? " ", // Dynamic username style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: widget .layoutColor, // Change this to any color // color: Colors.blueAccent, // Change this to any color ), ), ], ), ), ], ), ), ], ), // Padding( // padding: const EdgeInsets.all(8.0), // child: Divider( // color: Color(0xFFE6E7F5), // Change color // thickness: 0.5, // ), // ), SizedBox(height: 10), // Row( // children: [ // Expanded( // child: // ), // ], // ), isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: _buildTripRow(isMobile)) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: _buildTripRow(isMobile)), SizedBox( height: 15, ), isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: _buildCostCenter(isDesktop)) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: _buildCostCenter(isDesktop)), SizedBox( height: 8, ), isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildNonDescriptionColumn(), SizedBox(width: 25), _buildDescriptionColumn(isDesktop), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildNonDescriptionColumn(), SizedBox(height: 15), _buildDescriptionColumn(isDesktop), ], ), // Padding( // padding: const EdgeInsets.all(8.0), // child: Divider( // color: Color(0xFFE6E7F5), // Change color // thickness: 0.5, // ), // ), SizedBox( height: 20, ), if (validationErrors["services"] != null) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( validationErrors["services"]!, style: TextStyle( color: Colors.red, fontSize: 10, fontWeight: FontWeight.bold), ), ], ), Row( children: [ Expanded( child: DynamicItinerary( key: dynamicItineraryKey, hasAction: hasAction, tripType: _selectedTripType, apiData: apiData, apiDataForClass: apiDataForClass, apiCountryData: apiCountryData, onItineraryUpdate: handleItineraryUpdate, loginUser: selfId, selectedPlanData: planData, isViewMode: widget.isViewMode, )), // Wrap with Expanded if needed ], ), SizedBox(height: 15), ], ); // Row( // children: [ // isDesktop // ? Row( // mainAxisAlignment: MainAxisAlignment.end, // children: _buildSubmit(isDesktop), // ) // : Row( // mainAxisAlignment: MainAxisAlignment.center, // children: _buildSubmit(isDesktop), // ) // ], // ) }); } String? getSelectedCostCenterName() { if (selectedCostCenterId == null || apiCostData == null) return null; return apiCostData!.firstWhere( (item) => item['department_id'] == selectedCostCenterId, orElse: () => null, )?['name']; } /// Extracted helper function /// List _buildTripRow(bool isMobile) { List purposeList = apiData?['plan_is_billable'] ?? []; return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Trip Type *", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), isMobile ? SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: _buildTripType(isMobile), ), ) : Row( children: _buildTripType(isMobile), ), if (validationErrors["trip_type"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["trip_type"]!, style: TextStyle(color: Colors.red, fontSize: 10), ), ), ], ), SizedBox( width: 150, height: 5, ), // SizedBox( // width: 25, // height: 5, // ), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Is Billable ", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), Row( children: purposeList.map((item) { final isSelected = _selectedIsBillable == item['dropdown_key'].toString(); return Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: CustomTextFieldWrapper( color: Color(0xFFF5F5F5), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), layoutColor: widget.layoutColor, width: item['dropdown_key'] == 'some_key' ? 185 : 125, borderRadius: BorderRadius.circular(25), isFocused: isSelected, isDesktop: widget.isDesktop, child: GestureDetector( onTap: widget.isViewMode ? null : () { setState(() { _selectedIsBillable = item['dropdown_key'].toString(); }); print("SELECBILL - $_selectedIsBillable"); }, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item['dropdown_value'] ?? '', style: TextStyle( color: isSelected ? Colors.white : Colors.black, fontWeight: isSelected ? FontWeight.w500 : null, fontSize: 13), ), const SizedBox(width: 8), Container( width: 16, height: 16, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all( color: isSelected ? Colors.white : Colors.black, width: isSelected ? 2 : 1, ), ), child: isSelected ? Icon(Icons.rectangle, size: 8, color: Colors.white) : null, ), ], ), ), ), ); }).toList(), ), // Row( // children: purposeList.map((item) { // // return Row( // // children: [ // // Radio( // // // value: item['dropdown_key'], // Use dropdown_value as value // // // // value: item['dropdown_key'].toString(), // Convert to String // // groupValue: _selectedIsBillable, // // activeColor: Colors.blueAccent, // // onChanged: widget.isViewMode // // ? null // // : (value) { // // setState(() { // // _selectedIsBillable = value; // // }); // // print("SELECBILL - $_selectedIsBillable"); // // }, // // ), // // // // Text(item['dropdown_value'] ?? ''), // Display dropdown_value // // SizedBox(width: 20), // Spacing // // ], // // ); // }).toList(), // // // ), ]) ]; } List _buildCostCenter(bool isDesktop) { // if (apiData == null) { // return Center(child: CircularProgressIndicator()); // Show loading indicator // } // 'plan_purpose_of_travel' Starts ------------------------------------------------------ // List purposeList = apiData?['plan_purpose_of_travel'] ?? []; List> purposeList = List>.from( apiData?['plan_purpose_of_travel'] ?? []); List> dropdownItems = purposeList .map((item) => DropdownMenuItem( // value: item['dropdown_key'], value: item['dropdown_key']?.toString(), // value: item['dropdown_key'].toString(), child: Text(item['dropdown_value']), )) .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( // value: null, value: "1", child: Text("No options available", style: TextStyle(color: Colors.grey)), ), ); } selectedPurpose ??= dropdownItems.isNotEmpty ? dropdownItems.first.value.toString() : "No options"; print( "Dropdown Purpose List: ${dropdownItems.map((e) => e.value).toList()}"); print("Selected Purpose: $selectedPurpose"); // 'plan_functional_department' Starts --------------------------------------------- // List funcDeptList = apiData?['plan_functional_department'] ?? []; List> funcDeptList = List>.from( apiData?['plan_functional_department'] ?? []); List> dropdownFuncDeptItems = funcDeptList .map((item) => DropdownMenuItem( // value: item['dropdown_key'], value: item['dropdown_key']?.toString(), child: Text(item['dropdown_value']), )) .toList(); if (dropdownFuncDeptItems.isEmpty) { dropdownFuncDeptItems.add( DropdownMenuItem( // value: null, value: "1", child: Text("No options available", style: TextStyle(color: Colors.grey)), ), ); } // selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : null; selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : "No options"; print( "Dropdown Functional Department List: ${dropdownFuncDeptItems.map((e) => e.value).toList()}"); print("Selected Functional Department: $selectedFuncDept"); return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Cost Center *", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, // Dropdown doesn't use focus isDesktop: widget.isDesktop, child: SizedBox( height: 35, width: double.infinity, child: apiCostData == null ? Center( child: Transform.scale( scale: 0.5, child: CircularProgressIndicator(), ), ) : DropdownSearch( selectedItem: costCenterMap[selectedCostCenterId], enabled: !widget.isViewMode, popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Cost Center...", hintStyle: TextStyle(fontSize: 13, color: Colors.grey), contentPadding: EdgeInsets.symmetric(horizontal: 10), ), style: TextStyle(fontSize: 13)), menuProps: MenuProps( backgroundColor: Colors.white, ), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0), child: Text( item, style: TextStyle( fontSize: 13), // πŸ‘ˆ Set your desired text size here ), ), ), items: costCenterMap.values.toList(), // just names 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(() { selectedCostCenterId = costCenterMap.entries .firstWhere((entry) => entry.value == newValue) .key; }); }, ), ), // child: SizedBox( // height: 45, // Set appropriate height // child: DropdownButtonFormField( // value: selectedCostCenterId, // style: TextStyle(fontSize: 12), // decoration: InputDecoration( // border: InputBorder.none, // contentPadding: // EdgeInsets.symmetric(horizontal: 10), // Proper padding // ), // onChanged: widget.isViewMode // ? null // : (newValue) { // setState(() { // selectedCostCenterId = newValue; // }); // }, // items: apiCostData?.map>((item) { // return DropdownMenuItem( // value: item['department_id'], // ID as value // child: Text(item['name'] ?? "Unknown"), // ); // }).toList(), // ), // ), ), ], ), SizedBox( width: 25, height: 5, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Purpose of Trip *", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldWrapper( width: isDesktop ? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.85, isFocused: false, // Dropdown doesn't use focus isDesktop: widget.isDesktop, child: SizedBox( height: 35, // Set appropriate height child: apiData == null ? Center( child: CircularProgressIndicator()) // Show loading inside dropdown : DropdownSearch>( selectedItem: purposeList.firstWhere( (item) => item['dropdown_key'].toString() == selectedPurpose, orElse: () => {}, // βœ… Safe fallback ), items: purposeList, itemAsString: (Map item) => item['dropdown_value'], popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Purpose...", hintStyle: TextStyle(fontSize: 13, color: Colors.grey), contentPadding: EdgeInsets.symmetric(horizontal: 10), ), style: TextStyle(fontSize: 13)), menuProps: MenuProps( backgroundColor: Colors.white, ), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0), child: Text( item['dropdown_value'], style: TextStyle( fontSize: 13), // πŸ‘ˆ Set your desired text size here ), ), ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( contentPadding: EdgeInsets.symmetric( horizontal: 1, ), border: InputBorder.none, ), ), onChanged: widget.isViewMode ? null : (Map? newValue) { setState(() { selectedPurpose = newValue?['dropdown_key'].toString(); }); print("selectedPurpose - $selectedPurpose"); }, dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem?['dropdown_value'] ?? '', style: TextStyle(fontSize: 12), ), ), ), ), ), // CustomTextFieldWrapper( // isFocused: false, // Dropdown doesn't use focus // isDesktop: widget.isDesktop, // child: SizedBox( // height: 45, // Set appropriate height // child: apiData == null // ? Center( // child: // CircularProgressIndicator()) // Show loading inside dropdown // : DropdownButtonFormField( // value: selectedPurpose, // style: TextStyle(fontSize: 12), // decoration: InputDecoration( // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric( // horizontal: 10), // Proper padding // ), // onChanged: widget.isViewMode // ? null // : purposeList.isNotEmpty // ? (newValue) { // setState(() { // selectedPurpose = newValue; // }); // print( // "selectedPurpose - $selectedPurpose"); // } // : null, // items: dropdownItems, // ), // ), // ), ], ), SizedBox( width: 25, height: 5, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Functional Department", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, // Dropdown doesn't use focus isDesktop: widget.isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.85, // width: MediaQuery.of(context).size.width * 0.2, child: SizedBox( height: 35, // Set appropriate height child: apiData == null ? Center( child: CircularProgressIndicator()) // Show loading inside dropdown : DropdownSearch>( selectedItem: funcDeptList.firstWhere( (item) => item['dropdown_key'].toString() == selectedFuncDept, orElse: () => {}, // βœ… Safe fallback ), items: funcDeptList, itemAsString: (Map item) => item['dropdown_value'], popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search department...", hintStyle: TextStyle(fontSize: 13, color: Colors.grey), contentPadding: EdgeInsets.symmetric(horizontal: 10), ), style: TextStyle(fontSize: 13)), menuProps: MenuProps( backgroundColor: Colors.white, ), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0), child: Text( item['dropdown_value'], style: TextStyle( fontSize: 13), // πŸ‘ˆ Set your desired text size here ), ), ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 1), border: InputBorder.none, ), ), onChanged: widget.isViewMode ? null : (Map? newValue) { setState(() { selectedFuncDept = newValue?['dropdown_key'].toString(); }); print("selectedFuncDept - $selectedFuncDept"); }, dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem?['dropdown_value'] ?? '', style: TextStyle(fontSize: 12), ), ), ), ), ), ], ), ]; } List _buildPlanTrip(bool isDesktop) { List> options = [ {"title": "Self", "value": "Option 1"}, {"title": "Other Employee", "value": "Option 2"}, {"title": "Others", "value": "Option 3"}, ]; print(" layoutColor: ${widget.layoutColor}"); return options.map((option) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 0), child: CustomTextFieldWrapper( // color: Color(0xFFF4F4FB), color: Color(0xFFF5F5F5), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), layoutColor: widget.layoutColor, width: option["value"] == "Option 2" ? 185 : 125, // Adjust width conditionally borderRadius: BorderRadius.circular(25), isFocused: _selectedOption == option["value"], isDesktop: isDesktop, child: GestureDetector( onTap: widget.isViewMode ? null : () { setState(() { _selectedOption = option["value"]!; if (option["value"] == 'Option 2' || option["value"] == 'Option 3') { _showInputDialog(option["title"]!); } }); }, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( option["title"]!, style: TextStyle( fontSize: 13, color: _selectedOption == option["value"] ? Colors.white : Colors.black, fontWeight: _selectedOption == option["value"] ? FontWeight.w500 : null, ), ), Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.rectangle, // color: _selectedOption == option["value"] // ? Colors.blueAccent // : Colors.transparent, borderRadius: BorderRadius.circular(4), // Rounded rectangle border: Border.all( color: _selectedOption == option["value"] ? Colors.white : Colors.black, width: _selectedOption == option["value"] ? 2 : 1, ), ), child: _selectedOption == option["value"] ? Icon(Icons.rectangle, size: 8, color: Colors.white) : null, // Add checkmark if selected ), ], ), ), ), ); }).toList(); } List _buildTripType(bool isMobile) { return [ if (showDomestic == true) GestureDetector( onTap: widget.isViewMode ? null : () { setState(() { _selectedTripType = "1"; fetchTrainFlightClass(1); dynamicItineraryKey.currentState?.updateSelectedServices(); }); }, child: CustomTextFieldWrapper( color: Color(0xFFF4F4FB), layoutColor: widget.layoutColor, borderRadius: BorderRadius.circular(25), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), width: 130, isFocused: _selectedTripType == "1", isDesktop: widget.isDesktop, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Domestic", style: TextStyle( color: _selectedTripType == "1" ? Colors.white : Colors.black, fontWeight: _selectedTripType == "1" ? FontWeight.w500 : null, fontSize: 13), ), Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.rectangle, // color: _selectedOption == option["value"] // ? Colors.blueAccent // : Colors.transparent, borderRadius: BorderRadius.circular(4), // Rounded rectangle border: Border.all( color: _selectedTripType == "1" ? Colors.white : Colors.black, width: _selectedTripType == "1" ? 2 : 1, ), ), child: _selectedTripType == "1" ? Icon(Icons.rectangle, size: 8, color: Colors.white) : null, // Add checkmark if selected ), ], ), ), ), SizedBox(width: 20), if (showInternational) GestureDetector( onTap: widget.isViewMode ? null : () { setState(() { _selectedTripType = "2"; fetchTrainFlightClass(2); dynamicItineraryKey.currentState?.updateSelectedServices(); }); }, child: CustomTextFieldWrapper( color: Color(0xFFF4F4FB), layoutColor: widget.layoutColor, borderRadius: BorderRadius.circular(25), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), width: 150, // padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2), isFocused: _selectedTripType == "2", isDesktop: widget.isDesktop, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "International", style: TextStyle( fontSize: 13, color: _selectedTripType == "2" ? Colors.white : Colors.black, fontWeight: _selectedTripType == "2" ? FontWeight.w500 : null, ), ), Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.rectangle, // color: _selectedOption == option["value"] // ? Colors.blueAccent // : Colors.transparent, borderRadius: BorderRadius.circular(4), // Rounded rectangle border: Border.all( color: _selectedTripType == "2" ? Colors.white : Colors.black, width: _selectedTripType == "2" ? 2 : 1, ), ), child: _selectedTripType == "2" ? Icon(Icons.rectangle, size: 8, color: Colors.white) : null, // Add checkmark if selected ), ], ), // RadioListTile( // activeColor: Colors.blueAccent, // contentPadding: EdgeInsets.zero, // dense: true, // title: Text("International"), // value: "2", // groupValue: _selectedTripType, // onChanged: widget.isViewMode // ? null // : (value) { // setState(() { // _selectedTripType = value!; // }); // }, // ), ), ), ]; } Widget _buildNonDescriptionColumn() { return Column(children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Trip Name", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _isTripTitleFocused, isDesktop: widget.isDesktop, child: SizedBox( height: 35, child: TextField( focusNode: _tripTitleFocusNode, controller: _tripTitleController, style: TextStyle(fontSize: 12), enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Trip Name", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ), ], ), SizedBox( height: 8, ), ]); } Widget _buildDescriptionColumn(isDesktop) { return Column( children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Description", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _isdescriptionFocused, isDesktop: widget.isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.42 : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 35, child: TextField( focusNode: _descriptionFocusNode, controller: _descriptionController, style: TextStyle(fontSize: 12), enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Description", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), // CustomTextFieldWrapper( // isFocused: _isdescriptionFocused, // width: isDesktop // ? MediaQuery.of(context).size.width * 0.4 // : MediaQuery.of(context).size.width * 0.85, // isDesktop: widget.isDesktop, // child: SizedBox( // height: 35, // child: // TextField( // focusNode: _descriptionFocusNode, // controller: _descriptionController, // // maxLines: 1, // // keyboardType: TextInputType.multiline, // style: TextStyle(fontSize: 12), // enabled: !widget.isViewMode, // decoration: InputDecoration( // labelText: "Description", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 1), // ), // ), // ), // ), ], ), ], ), ], ); } List _buildSubmit1(isDesktop) { return [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.blueAccent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.blueAccent, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { widget.isApprover ? context.go('/approvallist') : context.go('/listPlan'); }, child: Text("Cancel")), SizedBox( width: 20, ), MouseRegion( cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: widget.isViewMode ? Colors.blueAccent : Colors.blueAccent, // Keep original color foregroundColor: widget.isViewMode ? Colors.white : 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: widget.isViewMode ? null : handleSubmit, // Disable when in view mode child: Text("Submit"), ), ) ]; } Widget _buildApprovalItem(String title, String name) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "$title : ", style: TextStyle( fontFamily: "Archivo", fontSize: 11, fontWeight: FontWeight.w500, color: Colors.black87, ), ), Expanded( child: Text( name, style: TextStyle( fontFamily: "Archivo", fontSize: 11, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), ], ); } Color getStatusColor(String status) { if (status == "Partially Approved") return Colors.yellow; if (status == "Approved") return Colors.green; if (status == "Completed") return Colors.green; if (status == "Rejected") return Colors.red; return Colors.grey; } List _buildApproverControls(bool isDesktop) { final statusText = isApproverApproved ? "Approved" : isApproverRejected ? "Rejected" : (statusValue ?? ""); bool hasApprovals = planStatusList.any( (item) => item.entries.any( (entry) => entry.key.contains('status') && entry.value != null && entry.value.toString().isNotEmpty, ), ); return [ Stack( clipBehavior: Clip.none, // allow tooltip to overflow children: [ if (statusValue != "") MouseRegion( onEnter: (_) => setState(() => isStatusExpanded = true), onExit: (_) => setState(() => isStatusExpanded = false), child: Container( decoration: BoxDecoration( border: Border.all(color: getStatusColor(statusText)), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.only( top: 8.0, bottom: 8.0, left: 15, right: 15), child: Text( statusText, style: TextStyle( fontFamily: "Roboto", fontWeight: FontWeight.w400, fontSize: 12, color: getStatusColor( isApproverApproved ? "Approved" : isApproverRejected ? "Rejected" : (statusValue ?? ""), ), ), ), ), ], ), ), ), // Tooltip if (isStatusExpanded) Positioned( top: -20, // Ensure the tooltip is above the container left: MediaQuery.of(context).size.width * 0.06, // align with label child: Material( // important: avoid clipping, give elevation elevation: 4, borderRadius: BorderRadius.circular(8), child: SizedBox( child: Container( width: isDesktop ? MediaQuery.of(context).size.width * 0.25 : MediaQuery.of(context).size.width, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, // color: Colors.transparent, border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!hasApprovals) Center( child: Text( "--- No Approvals ---", style: TextStyle( fontFamily: "Archivo", fontSize: 11, fontWeight: FontWeight.w500, color: Colors.black87, ), )), for (int i = 0; i < planStatusList.length; i++) ...[ if (planStatusList[i].entries.any((entry) => entry.key.contains('status') && entry.value != null && entry.value.toString().isNotEmpty)) ...[ _buildApprovalItem( "Approver ${i + 1}", planStatusList[i] .entries .firstWhere( (entry) => entry.key.contains('status'), orElse: () => MapEntry('', ''), ) .value .toString(), ), SizedBox(height: 6), ], ], ], ), ), ), ), ), // if (isStatusExpanded) // Positioned( // top: 20, // adjust how much above you want // left: 100, // child: Container( // margin: isDesktop // ? const EdgeInsets.only(left: 0, top: 0) // : const EdgeInsets.only(left: 5, top: 2), // padding: const EdgeInsets.all(12), // width: isDesktop // ? MediaQuery.of(context).size.width * 0.2 // : MediaQuery.of(context).size.width, // decoration: BoxDecoration( // // color: Color(0xFFF5F5F5), // color: Colors.white, // border: Border.all(color: Colors.white, width: 0.2), // borderRadius: BorderRadius.circular(8), // ), // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // if (!hasApprovals) // Center( // child: Text( // "--- No Approvals ---", // style: TextStyle( // fontFamily: "Archivo", // fontSize: 11, // fontWeight: FontWeight.w500, // color: Colors.black87, // ), // )), // for (int i = 0; i < planStatusList.length; i++) ...[ // if (planStatusList[i].entries.any((entry) => // entry.key.contains('status') && // entry.value != null && // entry.value.toString().isNotEmpty)) ...[ // _buildApprovalItem( // "Approver ${i + 1}", // planStatusList[i] // .entries // .firstWhere( // (entry) => entry.key.contains('status'), // orElse: () => MapEntry('', ''), // ) // .value // .toString(), // ), // SizedBox(height: 6), // ], // ], // ], // ), // ), // ), ], ), // Row( // mainAxisSize: MainAxisSize.min, // children: [ // Text( // isApproverApproved // ? "Approved" // : isApproverRejected // ? "Rejected" // : (statusValue ?? ""), // style: TextStyle( // fontFamily: "Archivo", // fontWeight: FontWeight.bold, // color: widget.layoutColor ?? Colors.grey, // ), // ), // SizedBox(width: 5), // MouseRegion( // onEnter: (_) => setState(() => isStatusExpanded = true), // onExit: (_) => setState(() => isStatusExpanded = false), // child: Icon( // Icons.approval_outlined, // size: 18, // color: isStatusExpanded ? Colors.green : Colors.grey, // ), // ), // ], // ), const SizedBox(height: 10, width: 10), if (widget.isApprover) GestureDetector( onTap: () { setState(() { isShowApprovalAction = !isShowApprovalAction; }); }, child: Icon( Icons.edit, size: 18, color: isShowApprovalAction ? Colors.green : Colors.grey, ), ), const SizedBox(height: 10, width: 10), if (isShowApprovalAction) Row( mainAxisSize: MainAxisSize.min, children: [ MouseRegion( cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( disabledBackgroundColor: statusValue == "Approved" ? Colors.green.shade100 : null, disabledForegroundColor: statusValue == "Approved" ? Colors.white : Colors.black, backgroundColor: isApproverApproved || (!isApproverApproved && !isApproverRejected && statusValue == "Approved") ? Colors.green : Colors.grey.shade100, foregroundColor: isApproverApproved || (!isApproverApproved && !isApproverRejected && statusValue == "Approved") ? Colors.white : Colors.black, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10), ), onPressed: widget.isViewMode ? null : () async { final confirmed = await showApproveDialog( context, widget.layoutColor ?? Colors.grey); if (confirmed == true) { setState(() { isApproverApproved = true; isApproverRejected = false; statusValue = "Approved"; }); callApproveAPI( selectedPlanId!, planData['user_id'], ); } }, child: Text( isApproverApproved || statusValue == "Approved" ? "Approved" : "Approve", style: TextStyle(fontSize: 12), ), ), ), const SizedBox(width: 10), MouseRegion( cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( disabledBackgroundColor: statusValue == "Rejected" ? Colors.redAccent.shade100 : null, disabledForegroundColor: statusValue == "Rejected" ? Colors.white : Colors.black, backgroundColor: isApproverRejected || (!isApproverApproved && !isApproverRejected && statusValue == "Rejected") ? Colors.redAccent : Colors.grey.shade100, foregroundColor: isApproverRejected || (!isApproverApproved && !isApproverRejected && statusValue == "Rejected") ? Colors.white : Colors.black, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10), ), onPressed: widget.isViewMode ? null : () async { final remarks = await showRejectDialog( context, widget.layoutColor ?? Colors.grey); if (remarks != null) { _remarksController.text = remarks; setState(() { isApproverApproved = false; isApproverRejected = true; statusValue = "Rejected"; }); callRejectAPI( selectedPlanId!, planData['user_id'], remarks); } }, child: Text( isApproverRejected || statusValue == "Rejected" ? "Rejected" : "Reject", style: TextStyle(fontSize: 12), ), ), ), ], ), ]; } List _buildPlanPdf() { return [ Column( children: [ InkWell( hoverColor: Colors.white, onTap: () { print('πŸ“„ PDF icon clicked!'); getPdfDownload(); }, child: Row( children: [ Text("Download PDF"), SizedBox( width: 10, ), Transform.scale( scale: 1.5, // 1.0 = normal, 1.5 = 50% bigger child: Image.asset( 'assets/images/IconsImg/planPdf_icon.png', width: 25, height: 25, // keep the real height small ), ), ], )), // SizedBox(height: 10), // small spacing ], ), ]; } void _showInputDialog(String title) { showDialog( context: context, builder: (BuildContext context) { return UserSelectionDialog( title: title, onSubmit: (input, userId, isTraveller) { setState(() { otherUserName = input; selectedplanUserId = userId; selectedIstravelUser = isTraveller; }); print("USer entered : $otherUserName $userId $isTraveller"); getSelectedPlanFor(); }, layoutColorForUser: widget.layoutColor!, ); }); } }