import 'dart:convert'; import 'dart:async'; import 'dart:typed_data'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:super_tooltip/super_tooltip.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 '../../widgets/saving_loader.dart'; import '../approvals/approval_dialogs.dart'; import '../dialog/user_selection_dialog.dart'; import '../itnerary/flights.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; final FocusNode focusNode = FocusNode(); bool isFocused = false; @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, backgroundColor: Color(0xFFf5f5f5), // backgroundColor: Color(0xFFFCFCFC), appBar: CustomAppBar(isDesktop: isDesktop), 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(0), 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 String approverId = args['approverId'] ?? ""; final String delegaterId = args['delegaterId'] ?? ""; 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 approverId - $approverId"); print("RECived delegateId - $delegaterId"); return Container( margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, padding: const EdgeInsets.all(8), decoration: BoxDecoration( // color: Colors.amber, // color: bodyColor, color: isDesktop ? Colors.white : Color(0xFFFCFCFC), // 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, approverId: approverId, delegaterId: delegaterId, ), ), ), ), ), 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: () { final currentUri = GoRouterState.of( context, ).uri.toString(); // βœ… safer than `.location` print("currentUri - $currentUri"); if (currentUri == "/allTrips/trips") { context.go('/listAllPlan'); } else if (currentUri == "/createPlan") { context.go('/listPlan'); } else { 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; final String approverId; final String delegaterId; const CreateNewPlan({ super.key, required this.isDesktop, required this.bodyColor, required this.layoutColor, required this.selectedPlanData, required this.isViewMode, required this.isApprover, required this.approverId, required this.delegaterId, }); @override CreateNewPlansState createState() => CreateNewPlansState(); } class CreateNewPlansState extends State { final GlobalKey dynamicItineraryKey = GlobalKey(); final GlobalKey flightScreenKey = GlobalKey(); late final ValueNotifier flightTripTypeNotifier; 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 _isdescriptionFocused = false; bool _isTripTitleFocused = 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 = []; // Declare tooltip controller late SuperTooltip tooltip; 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; String? temporaryMessage; //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(); flightTripTypeNotifier = ValueNotifier(null); 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 handleSelectedUser() { if (widget.selectedPlanData != null) { setState(() { String userId = widget.selectedPlanData['user_id'] ?? ''; String userName = widget.selectedPlanData['user_name'] ?? ''; String travellerId = widget.selectedPlanData['traveller_id'] ?? ''; String travellerName = widget.selectedPlanData['traveller_name'] ?? ''; print("TestplanUsrId $userId --- $travellerId"); if (travellerId.isNotEmpty && travellerId != "0") { print("TestplanUsrId1: $travellerId"); planTravlrId = travellerId; _selectedOption = "Option 3"; otherUserName = travellerName; } else if (userId.isNotEmpty && userId != "0") { print("TestplanUsrId : $userId"); print("TestplanUsrIdSelf : $selfId"); _selectedOption = (selfId != userId) ? "Option 2" : "Option 1"; otherUserName = userName; } }); } } 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']; flightTripTypeNotifier.value = 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'); } } Future getSelectedPlanFor() async { var userTripId; // if (!mounted) return; print("getSelectedPlanFor"); setState(() { if (selectedplanUserId != null) { print("Is Not USER ID - $planUsrId "); if (selectedIstravelUser!) { planUsrId = ""; planTravlrId = selectedplanUserId; userTripId = selectedplanUserId; } else { planUsrId = selectedplanUserId; planTravlrId = ""; userTripId = selectedplanUserId; } } else { print("Is USER ID - $planUsrId "); planUsrId = selfId; planTravlrId = ""; _selectedOption = "Option 1"; userTripId = selfId; } }); final prefs = await SharedPreferences.getInstance(); await prefs.setString('trip_planned_user', userTripId); 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"); // handleSelectedUser(); getSelectedPlanFor(); setTripPlanAction(); handleSelectedUser(); // handleUpdateData(); } 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("CostCenterdropdoen - $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['cost_center_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, "trip_title": _tripTitleController.text, "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] = "Required"; // "${entry.key.replaceAll('_', ' ').toUpperCase()} 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, // ); bool anyServiceSelected = serviceLists.any((list) { return list != null && list.any((entry) => entry['is_active'].toString() == "1"); }); if (!anyServiceSelected) { validationErrors["services"] = "Please select at least one service"; setState(() { temporaryMessage = "Please select at least one service"; }); // Clear message after 3 seconds Future.delayed(Duration(seconds: 3), () { if (mounted) { setState(() { temporaryMessage = null; }); } }); } 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": widget.approverId, "delegation_user_id": widget.delegaterId, }, methodName: 'Plan Approval', ); } Future callRejectAPI( String planId, String userId, String remarks, ) async { await postToAPI( endpoint: '/api/plans/rejectPlan', data: { "plan_id": planId, "user_id": widget.approverId, "delegation_user_id": widget.delegaterId, "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'], data['approverId'], data['delegaterId'], isViewMode: false, isApprover: true, ); context.go('/approvallist'); } else { print("$methodName failed. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print("Error in $methodName: $e"); } } void handleSubmit() { setState(() async { if (validateForm() && temporaryMessage == null) { // 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}, " // " "); showDialog( context: context, barrierDismissible: false, builder: (context) => const SavingLoader(), ); // postPlanData(planData); await postPlanData(planData); // Close loading dialog (ONLY if still mounted) if (mounted) Navigator.of(context, rootNavigator: true).pop(); } }); } 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}"); final currentUri = GoRouterState.of( context, ).uri.toString(); // βœ… safer than `.location` print("currentUri - $currentUri"); if (currentUri == "/allTrips/trips") { context.go('/listAllPlan'); } else if (currentUri == "/createPlan") { context.go('/listPlan'); } else { widget.isApprover ? context.go('/approvallist') : context.go('/listPlan'); } } 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, // mainAxisAlignment: MainAxisAlignment.end, children: [ _buildTripName(isDesktop), if (isDesktop) Spacer(), ..._buildApproverControls(isDesktop), // Text( // widget.isViewMode // ? "View Plan" // : (selectedPlanId != null && // selectedPlanId!.isNotEmpty // ? "Update Plan" // : "New Plan"), // style: TextStyle(fontSize: 18), // ), // Spacer(), if (statusValue != "") ..._buildPlanPdf(isDesktop), ], ) : Column( children: [ Row( // crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ ..._buildApproverControls( isDesktop, ), if (statusValue != "") ..._buildPlanPdf(isDesktop), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [_buildTripName(isDesktop)], ), ], ), ); }, ), // 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: InputDecoration( hintText: "Please enter remarks...", hintStyle: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), border: InputBorder.none, isDense: true, ), maxLines: null, ), ), ), ], ), // if (widget.isApprover || isStatusExpanded) Divider(thickness: 0.1, color: Colors.blueGrey), if (widget.isApprover) SizedBox(height: 5), Text( "Planning This Trip For*", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 6), isDesktop ? SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row(children: _buildPlanTrip(isDesktop)), ) : Row(children: _buildPlanTrip(isDesktop)), SizedBox(height: 7), // Text( // otherUserName ?? userName ?? " ", // Your label // // style: GoogleFonts.poppins( // fontSize: 11, // fontWeight: FontWeight.w400, // color: widget.layoutColor, // ), // ), Text.rich( TextSpan( text: "Trip Planned User : ", // Static text style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w400, color: Color(0xFF212121), // color: Color(0xFF575A74), // Default color ), children: [ TextSpan( text: otherUserName ?? userName ?? " ", // Dynamic username style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w400, color: widget .layoutColor, // Change this to any color // color: Colors.blueAccent, // Change this to any color ), ), ], ), ), SizedBox(height: 5), ], ), ), ], ), // 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 (temporaryMessage != null) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( // validationErrors["services"]!, temporaryMessage!, style: GoogleFonts.poppins( color: Colors.red, fontSize: 12, fontWeight: FontWeight.w400, ), ), ], ), if (temporaryMessage != null) SizedBox(height: 10), Row( children: [ Expanded( child: DynamicItinerary( key: dynamicItineraryKey, flightScreenKey: flightScreenKey, tripTypeNotifier: flightTripTypeNotifier, 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']; // } final List> allTripTypes = [ {"dropdown_key": "1", "dropdown_value": "Domestic"}, {"dropdown_key": "2", "dropdown_value": "International"}, ]; List> getFilteredTripTypes() { if (!showDomestic && !showInternational) return []; return allTripTypes.where((item) { if (item['dropdown_key'] == "1" && showDomestic) return true; if (item['dropdown_key'] == "2" && showInternational) return true; return false; }).toList(); } List _buildTripRow(bool isMobile) { List purposeList = apiData?['plan_is_billable'] ?? []; List> tripTypeList = [ {"dropdown_key": "1", "dropdown_value": "Domestic"}, {"dropdown_key": "2", "dropdown_value": "International"}, ]; return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Trip Type *", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 35, width: double.infinity, child: DropdownSearch>( popupProps: PopupProps.menu( showSearchBox: false, // Optionally enable search fit: FlexFit.loose, menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) { return Padding( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 8, ), child: Text( item['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, // πŸ‘ˆ Smaller font size color: Colors.black, ), ), ); }, ), dropdownDecoratorProps: const DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, // No underline contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select Trip Type", style: GoogleFonts.poppins( color: Colors.grey, fontSize: 13, ), ); } return Text( selectedItem['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ); }, selectedItem: tripTypeList.firstWhere( (item) => item['dropdown_key'] == _selectedTripType, orElse: () => {}, ), itemAsString: (item) => item['dropdown_value'] ?? '', items: getFilteredTripTypes(), // items: // [ // {"dropdown_key": "1", "dropdown_value": "Domestic"}, // {"dropdown_key": "2", "dropdown_value": "International"}, // ].map>((item) { // return Map.from(item); // }).toList(), onChanged: widget.isViewMode ? null : (Map? newItem) { if (newItem != null) { setState(() { _selectedTripType = newItem['dropdown_key'].toString(); // Notify FlightScreen flightTripTypeNotifier.value = _selectedTripType; print( "flightTripTypeNotifier- ${flightTripTypeNotifier.value}", ); fetchTrainFlightClass( int.parse(_selectedTripType!), ); dynamicItineraryKey.currentState ?.updateSelectedServices(); }); } }, ), ), ), // 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: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ], ), SizedBox(width: 25, height: 5), // SizedBox( // width: 25, // height: 5, // ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Is Billable ", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.2 : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 35, width: double.infinity, child: DropdownSearch>( popupProps: PopupProps.menu( showSearchBox: false, fit: FlexFit.loose, menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) { return Padding( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 8, ), child: Text( item['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ); }, ), dropdownDecoratorProps: const DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), selectedItem: purposeList .cast>() .firstWhere( (item) => item['dropdown_key'] == _selectedIsBillable, orElse: () => {}, ), dropdownButtonProps: const DropdownButtonProps( icon: Icon(Icons.arrow_drop_down), ), // itemAsString: (item) => item['dropdown_value'] ?? '', // items: purposeList.cast>(), dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select Billable", // fallback text style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ); }, items: purposeList.map>((item) { return Map.from( item, ); // Ensuring each item is properly cast }).toList(), onChanged: widget.isViewMode ? null : (Map? newItem) { if (newItem != null) { setState(() { _selectedIsBillable = newItem['dropdown_key'].toString(); }); } }, ), ), ), // 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(), // ), ], ), ]; } 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: GoogleFonts.poppins(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: GoogleFonts.poppins( 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), hintStyle: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), contentPadding: EdgeInsets.symmetric( horizontal: 10, ), ), style: GoogleFonts.poppins(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: GoogleFonts.poppins( 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), contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select Purpose", // fallback text style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem ?? '', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ); }, // 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(), // ), // ), ), if (validationErrors["cost_center_id"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["cost_center_id"]!, style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ], ), SizedBox(width: 25, height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Purpose of Trip *", // Your label style: GoogleFonts.poppins( 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: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), contentPadding: EdgeInsets.symmetric( horizontal: 10, ), ), style: GoogleFonts.poppins(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: GoogleFonts.poppins( fontSize: 13, ), // πŸ‘ˆ Set your desired text size here ), ), ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), border: InputBorder.none, ), ), onChanged: widget.isViewMode ? null : (Map? newValue) { setState(() { selectedPurpose = newValue?['dropdown_key'].toString(); }); print("selectedPurpose - $selectedPurpose"); }, dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select Purpose", // fallback text style: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, // πŸ‘ˆ Small font size for selected item color: Colors.black, ), ); }, // dropdownBuilder: (context, selectedItem) => Align( // alignment: Alignment.centerLeft, // child: Text( // selectedItem?['dropdown_value'] ?? '', // style: TextStyle(fontSize: 12), // ), // ), ), ), ), if (validationErrors["purpose_of_travel"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["purpose_of_travel"]!, style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), // 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: GoogleFonts.poppins( 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: GoogleFonts.poppins( fontSize: 13, color: Colors.grey, ), contentPadding: EdgeInsets.symmetric( horizontal: 10, ), ), style: GoogleFonts.poppins( fontSize: 13, // πŸ‘ˆ Small font size for selected item ), ), 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: GoogleFonts.poppins( fontSize: 13, // πŸ‘ˆ Small font size for selected item ), // πŸ‘ˆ Set your desired text size here ), ), ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), border: InputBorder.none, ), ), onChanged: widget.isViewMode ? null : (Map? newValue) { setState(() { selectedFuncDept = newValue?['dropdown_key'].toString(); }); print("selectedFuncDept - $selectedFuncDept"); }, dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty) { return const Text( "Select Purpose", // fallback text style: TextStyle( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, // πŸ‘ˆ Small font size for selected item color: Colors.black, ), ); }, // dropdownBuilder: (context, selectedItem) => Align( // alignment: Alignment.centerLeft, // child: Text( // selectedItem?['dropdown_value'] ?? '', // style: TextStyle(fontSize: 12), // ), // ), ), ), ), if (validationErrors["functional_department"] != null) Padding( padding: EdgeInsets.only(top: 4), child: Text( validationErrors["functional_department"]!, style: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ], ), ]; } List _buildPlanTrip(bool isDesktop) { List> options = [ {"title": "Self", "value": "Option 1"}, {"title": "Other Employee", "value": "Option 2"}, {"title": "Others (Non Employee)", "value": "Option 3"}, ]; return [ CustomTextFieldWrapper( isFocused: false, isDesktop: isDesktop, layoutColor: widget.layoutColor, child: SizedBox( height: 35, width: double.infinity, child: DropdownSearch( selectedItem: options.firstWhere( (opt) => opt['value'] == _selectedOption, )['title'], enabled: !widget.isViewMode, items: options.map((opt) => opt['title']!).toList(), popupProps: PopupProps.menu( showSearchBox: false, menuProps: const MenuProps(backgroundColor: Colors.white), constraints: BoxConstraints(maxHeight: 100), itemBuilder: (context, item, isSelected) => Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0, ), child: Text(item, style: GoogleFonts.poppins(fontSize: 13)), ), ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), dropdownBuilder: (context, selectedItem) { return Text( selectedItem ?? "Select Purpose", style: GoogleFonts.poppins( fontSize: 12, color: selectedItem == null ? Colors.grey : Colors.black, ), ); }, onChanged: (String? newTitle) { if (newTitle == null) return; final selected = options.firstWhere( (opt) => opt["title"] == newTitle, ); setState(() { _selectedOption = selected["value"]!; if (_selectedOption == "Option 2" || _selectedOption == "Option 3") { _showInputDialog(selected["title"]!); } else if (_selectedOption == "Option 1") { otherUserName = userName; } }); }, ), ), ), ]; } List _buildPlanTrip1(bool isDesktop) { List> options = [ {"title": "Self", "value": "Option 1"}, {"title": "Other Employee", "value": "Option 2"}, {"title": "Others (Non Employee)", "value": "Option 3"}, ]; print(" layoutColor: ${widget.layoutColor}"); return options.map((option) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 2, 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(10), 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: GoogleFonts.poppins( 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: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), // CustomTextFieldWrapper( // isFocused: false, // // isFocused: _isdescriptionFocused, // isDesktop: widget.isDesktop, // width: isDesktop // ? MediaQuery.of(context).size.width * 0.4 // : 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: false, isFocused: _isdescriptionFocused, // width: isDesktop // ? MediaQuery.of(context).size.width * 0.38 // : MediaQuery.of(context).size.width * 0.85, isDesktop: widget.isDesktop, child: SizedBox( height: 55, child: TextField( focusNode: _descriptionFocusNode, controller: _descriptionController, maxLines: 2, keyboardType: TextInputType.multiline, style: TextStyle(fontSize: 12), enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Description", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), // 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 _buildTripName(bool isDesktop) { return SizedBox( width: isDesktop ? MediaQuery.of(context).size.width * 0.2 : 180, height: 35, child: TextField( focusNode: _tripTitleFocusNode, controller: _tripTitleController, // style: TextStyle(fontSize: 12), style: GoogleFonts.poppins( fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w600, color: Colors.black, ), enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Trip Name *", labelStyle: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), errorText: validationErrors["trip_title"], // <--- add this line errorStyle: GoogleFonts.poppins(fontSize: 12, color: Colors.red), ), ), ); } 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 [ // 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; // // }); // }, onTap: () async { final result = await showApprovalDialog( context, widget.layoutColor ?? Colors.grey, ); if (result == 'approved') { // User approved setState(() { isApproverApproved = true; isApproverRejected = false; statusValue = "Approved"; }); callApproveAPI(selectedPlanId!, planData['user_id']); } else if (result is String) { // User rejected with remarks _remarksController.text = result; setState(() { isApproverApproved = false; isApproverRejected = true; statusValue = "Rejected"; }); callRejectAPI(selectedPlanId!, planData['user_id'], result); } }, child: Icon( Icons.edit, size: 18, color: isShowApprovalAction ? Colors.green : Colors.grey, ), ), // const SizedBox(height: 10, width: 4), // 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), // ), // ), // ), // ], // ), const SizedBox(height: 10, width: 10), 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: 6.0, bottom: 6.0, left: 15, right: 15, ), child: Text( statusText, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: getStatusColor( isApproverApproved ? "Approved" : isApproverRejected ? "Rejected" : (statusValue ?? ""), ), ), // style: TextStyle( // fontFamily: "Roboto", // fontWeight: FontWeight.w400, // fontSize: 12, // // ), ), ), ], ), ), ), // Tooltip if (isStatusExpanded) Positioned( right: 0, top: 45, // 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), // ], // ], // ], // ), // ), // ), ], ), const SizedBox(height: 10, width: 10), ]; } List _buildPlanPdf(isDesktop) { return [ Column( children: [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), foregroundColor: Colors.white, disabledBackgroundColor: Color(0xFF114D8B), disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Color(0xFF114D8B), width: 2), ), padding: isDesktop ? EdgeInsets.only( top: 6.0, bottom: 6.0, left: 15, right: 15, ) : EdgeInsets.symmetric(horizontal: 15, vertical: 10), ), onPressed: () { getPdfDownload(); }, child: Row( mainAxisSize: MainAxisSize.min, // Ensures content fits nicely children: [ if (isDesktop) Text( "Download PDF", style: GoogleFonts.poppins( fontSize: isDesktop ? 13 : 11, // fontWeight: // FontWeight.w500, ), // style: TextStyle(fontSize: isDesktop ? 13 : 11), ), if (isDesktop) SizedBox(width: 8), // spacing between icon and text Icon(Icons.download_rounded, size: 15, color: Colors.white), ], ), ), // 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(); }, onClose: () { print("Choosede Clsoes"); fetchUserDetails(); }, layoutColorForUser: widget.layoutColor!, ); }, ); } }