ts-tat/lib/Screens/plans/create_plans.dart
2025-10-08 11:37:26 +05:30

4324 lines
159 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'dart:async';
import 'dart:typed_data';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/services.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<CreatePlan> {
final GlobalKey<CreateNewPlansState> _createPlanKey =
GlobalKey<CreateNewPlansState>();
Color layoutColor = Colors.redAccent;
Color bodyColor = Colors.white;
final FocusNode focusNode = FocusNode();
bool isFocused = false;
late FocusNode _tripTitleFocusNode;
@override
void initState() {
super.initState();
_checkAuthAndLoadData();
// loadInitialData();
}
void _checkAuthAndLoadData() async {
final String? token = await getToken(); // Your async function to get token
if (token == null || token.isEmpty) {
// Token doesn't exist → redirect to login
context.go(
"/",
); // or use: router.go("/") if you're using `GoRouter` directly
return;
}
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<String, dynamic>? ?? {};
// 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 String approverStatus = args['approver_status'] ?? "";
final Map<String, dynamic> planData =
args['planData'] as Map<String, dynamic>? ?? {};
// print("isViewMode: $isViewMode");
// final bool isViewMode = true;
// final planData = GoRouterState.of(context).extra as Map<String, dynamic>? ?? {};
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,
approverStatus: approverStatus,
),
),
),
),
),
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<Widget> _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 if (currentUri == "/approver/plans") {
context.go('/approvallist');
} else if (currentUri == "/travelagent/trips") {
context.go('/listTravelAgentPlan');
} 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 String? approverStatus;
final Color? bodyColor;
final Color? layoutColor;
final Map<String, dynamic> 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.approverStatus,
required this.approverId,
required this.delegaterId,
});
@override
CreateNewPlansState createState() => CreateNewPlansState();
}
class CreateNewPlansState extends State<CreateNewPlan> {
final GlobalKey<DynamicItineraryState> dynamicItineraryKey =
GlobalKey<DynamicItineraryState>();
final GlobalKey<FlightScreenState> flightScreenKey =
GlobalKey<FlightScreenState>();
late final ValueNotifier<String?> flightTripTypeNotifier;
final ApiService apiService = ApiService();
final TextEditingController _tripTitleController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
final TextEditingController _soNumberController = TextEditingController();
final TextEditingController _excepntldescriptionController =
TextEditingController();
final TextEditingController _remarksController = TextEditingController();
// final FocusNode _tripTitleFocusNode = FocusNode();
// final FocusNode _descriptionFocusNode = FocusNode(); // Declare FocusNode
// bool _isdescriptionFocused = false;
// bool _isTripTitleFocused = false;
Color? layoutColor;
late String _selectedOption = "Option 1";
// late String? _selectedIsBillable = "Billable";
bool isStatusExpanded = false;
bool isShowApprovalAction = false;
String? selectedPlanId;
String? approverStatus;
String? userDetails;
String? userName;
String? selfId;
String? otherUserName;
String? selectedplanUserId;
bool? selectedIstravelUser;
late Color layoutColorForUser;
Map<String, dynamic>? apiData; // Store API response here
Map<String, dynamic>? apiDataForClass; // Store API response here
List<dynamic>? apiCountryData;
List<dynamic>? apiCostData; // Store API response here
bool isLoading = true; // Track loading state
String? TripPlanAction;
bool showDomestic = false;
bool showInternational = false;
bool hasAction = true;
bool hasSoNumber = false;
bool hasExceptionalClass = false;
bool hasExceptionalClassInUpdate = false;
late Map<String, String> costCenterMap;
List<String> costCenterIds = [];
// List<dynamic> apiCostData = []; // if youre not already using this
late Map<String, String> purposeMap;
List<String> 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;
bool hasUpdateVal = false;
Map<String, String?> validationErrors = {};
// List<Map<String, dynamic>> miscellaneousList = [{"special_request": 1, "comments": "posta", "indx": 1}];
List<Map<String, dynamic>> miscellaneousList = [];
List<Map<String, dynamic>> visaList = [];
List<Map<String, dynamic>> insuranceList = [];
List<Map<String, dynamic>> accommodationList = [];
List<Map<String, dynamic>> trainList = [];
List<Map<String, dynamic>> flightList = [];
List<Map<String, dynamic>> busList = [];
List<Map<String, dynamic>> taxiList = [];
List<Map<String, dynamic>> forexList = [];
List<Map<String, dynamic>> planStatusList = [];
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
late bool isApproverApproved = false;
late bool isApproverRejected = false;
String? temporaryMessage;
//Getter Method
Map<String, dynamic> 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,
"exceptional_plan_reason": _excepntldescriptionController.text,
"functional_department": selectedFuncDept,
"so_number": _soNumberController.text,
// "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,
};
List<String> dataHeader = [
"trip_planned",
"trip_type",
"cost_center_id",
"is_billable",
"purpose_of_travel",
"description",
"excepntldescription",
"functional_department",
"so_number",
];
void handleItineraryUpdate(String type, List<Map<String, dynamic>> 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");
}
void handleExcentionalClass(bool val) {
print("handleExcentionalClass - $val");
setState(() {
hasExceptionalClass = val;
});
}
@override
void initState() {
super.initState();
print("approverStatus - ${widget.approverStatus}");
WidgetsBinding.instance.addPostFrameCallback((_) {
loadInitialData();
dynamicItineraryKey.currentState?.updateSelectedServices();
});
// if (widget.selectedPlanData != null) {
// hasUpdateVal = true;
// }
if (widget.approverStatus == "Approval pending") {
print("Status approver - Approval Pending");
}
flightTripTypeNotifier = ValueNotifier(null);
for (var field in dataHeader) {
focusNodes["${field}FocusNode"] = FocusNode();
focusStates["${field}Focused"] = false;
}
// trip_titleFocusNode.addListener(() {
// setState(() {}); // Rebuild when focus changes
// });
print("Focus Nodes KeysII: ${focusNodes.keys.toList()}");
print("Focus States KeysII: ${focusStates.keys.toList()}");
for (var key in focusNodes.keys) {
_addFocusListener(focusNodes[key]!, (focus) {
setState(() {
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
});
});
}
fetchUserDetails();
fetchPlans();
fetchCostCenter();
fetchCountryList();
print("hasExceptionalClass - $hasExceptionalClass");
print("hasExceptionalClassInUpdate - $hasExceptionalClassInUpdate");
// _tripTitleFocusNode.addListener(() {
// setState(() {
// _isTripTitleFocused = _tripTitleFocusNode.hasFocus;
// });
// });
//
// _descriptionFocusNode.addListener(() {
// setState(() {
// _isdescriptionFocused = _descriptionFocusNode.hasFocus;
// });
// });
handleUpdateData();
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
setState(() {
layoutColor =
layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
});
}
@override
void dispose() {
for (var node in focusNodes.values) {
node.dispose();
}
// _tripTitleFocusNode.dispose();
// _descriptionFocusNode.dispose();
super.dispose();
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
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;
hasUpdateVal = true;
} else if (userId.isNotEmpty && userId != "0") {
print("TestplanUsrId : $userId");
print("TestplanUsrIdSelf : $selfId");
planUsrId = userId;
print("TestplanUsrId2 : $planUsrId");
_selectedOption = (selfId != userId) ? "Option 2" : "Option 1";
otherUserName = userName;
hasUpdateVal = true;
}
});
}
}
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'] ?? '';
_soNumberController.text = widget.selectedPlanData['so_number'] ?? '';
if (_soNumberController.text != "") {
hasSoNumber = true;
}
_excepntldescriptionController.text =
widget.selectedPlanData['exceptional_plan_reason'] ?? '';
if (_excepntldescriptionController.text != "" &&
_excepntldescriptionController.text != null) {
hasExceptionalClassInUpdate = true;
print("hasExceptionalClass1 - $hasExceptionalClass");
print("hasExceptionalClassInUpdate1 - $hasExceptionalClassInUpdate");
}
_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<Map<String, dynamic>>.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<Map<String, dynamic>>.from(
widget.selectedPlanData['flight'] ?? [],
);
accommodationList = List<Map<String, dynamic>>.from(
widget.selectedPlanData['accomodation'] ?? [],
);
busList = List<Map<String, dynamic>>.from(
widget.selectedPlanData['bus'] ?? [],
);
taxiList = List<Map<String, dynamic>>.from(
widget.selectedPlanData['taxi'] ?? [],
);
trainList = List<Map<String, dynamic>>.from(
widget.selectedPlanData['train'] ?? [],
);
visaList = List<Map<String, dynamic>>.from(
widget.selectedPlanData['visa'] ?? [],
);
forexList = List<Map<String, dynamic>>.from(
widget.selectedPlanData['forex'] ?? [],
);
insuranceList = List<Map<String, dynamic>>.from(
widget.selectedPlanData['insurance'] ?? [],
);
miscellaneousList = List<Map<String, dynamic>>.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<void> 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',
'app-signature': 'ts-traveltool-2025-signature-123456',
},
);
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 == 403) {
print("403-FORB");
await apiService.logout(context);
return null;
// throw Exception('Failed to load users');
}
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<void> getSelectedPlanFor() async {
var userTripId;
// if (!mounted) return;
print("getSelectedPlanFor");
setState(() async {
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 {
print('FetchHandleUSe');
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();
}
void fetchUsrDtlFromSelectedTripUser() async {
// final details = await getUserDetails();
TripPlanAction = await getTripPlanActionFromSelectedUsr();
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<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
Future<String?> getUserId() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('userId');
}
Future<Map<String, String>?> 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<void> 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',
'app-signature': 'ts-traveltool-2025-signature-123456',
},
);
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<String, dynamic> 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 if (response.statusCode == 403) {
print("403-FORB");
await apiService.logout(context);
return null;
// throw Exception('Failed to load users');
}
else {
throw Exception('Failed to load plans');
}
}
Future<void> 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',
'app-signature': 'ts-traveltool-2025-signature-123456',
},
);
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<dynamic> 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;
print("apiCostData...1");
costCenterMap = {
for (var item in apiCostData!)
// item['department_id'].toString(): item['name'].toString(),
item['cost_center_id'].toString(): item['name'].toString(),
};
print("apiCostData...122");
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 if (response.statusCode == 403) {
print("403-FORB");
await apiService.logout(context);
return null;
// throw Exception('Failed to load users');
}
else {
throw Exception('Failed to load plans');
}
}
Future<void> 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',
'app-signature': 'ts-traveltool-2025-signature-123456',
},
);
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<dynamic> plansJson = data['data']; // 'data' is a Map, not a List
if (data['data'] is List) {
List<dynamic> 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 if (response.statusCode == 403) {
print("403-FORB");
await apiService.logout(context);
return null;
// throw Exception('Failed to load users');
}
else {
throw Exception('Failed to load plans');
}
}
Future<void> 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',
'app-signature': 'ts-traveltool-2025-signature-123456',
},
);
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<String, dynamic> 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 if (response.statusCode == 403) {
print("403-FORB");
await apiService.logout(context);
return null;
// throw Exception('Failed to load users');
}
else {
throw Exception('Failed to load plans');
}
}
// Handle Submit
bool validateForm() {
print("validateForm");
validationErrors.clear(); // Clear previous errors
print("validateForm.....1");
// 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";
}
print("validateForm.....2");
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,
if (hasExceptionalClass)
"exceptional_plan_reason": _excepntldescriptionController.text,
if (hasSoNumber) "so_number": _soNumberController.text,
};
print("validateForm.....3");
for (var entry in requiredFields.entries) {
if (entry.value == null || entry.value!.isEmpty) {
validationErrors[entry.key] = "Required";
// "${entry.key.replaceAll('_', ' ').toUpperCase()} Required";
}
}
print("validateForm.....1");
// 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<void> 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<void> 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<void> postToAPI({
required String endpoint,
required Map<String, dynamic> 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',
'app-signature': 'ts-traveltool-2025-signature-123456',
},
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'],
// data['approver_status'],
//
// isViewMode: false,
// isApprover: true,
// );
// Close loading dialog (ONLY if still mounted)
if (mounted) Navigator.of(context, rootNavigator: true).pop();
context.go('/approvallist');
}
else if (response.statusCode == 403) {
print("403-FORB");
await apiService.logout(context);
return null;
// throw Exception('Failed to load users');
}
else {
print("$methodName failed. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print("Error in $methodName: $e");
}
}
Future<bool> serviceHasData() async {
return miscellaneousList.isNotEmpty ||
visaList.isNotEmpty ||
insuranceList.isNotEmpty ||
accommodationList.isNotEmpty ||
trainList.isNotEmpty ||
flightList.isNotEmpty ||
busList.isNotEmpty ||
taxiList.isNotEmpty ||
forexList.isNotEmpty;
}
void clearAllServiceLists() async {
setState(() {
miscellaneousList = [];
visaList = [];
insuranceList = [];
accommodationList = [];
trainList = [];
flightList = [];
busList = [];
taxiList = [];
forexList = [];
planData['miscellaneous'] = [];
planData['visa'] = [];
planData['insurance'] = [];
planData['accommodation'] = [];
planData['train'] = [];
planData['flight'] = [];
planData['bus'] = [];
planData['taxi'] = [];
planData['forex'] = [];
});
// Debugging output
print("After clearing:");
print("After clearing: $planData");
print("miscellaneousList: ${miscellaneousList.length}");
print("visaList: ${visaList.length}");
print("insuranceList: ${insuranceList.length}");
print("accommodationList: ${accommodationList.length}");
print("trainList: ${trainList.length}");
print("flightList: ${flightList.length}");
print("busList: ${busList.length}");
print("taxiList: ${taxiList.length}");
print("forexList: ${forexList.length}");
}
Future<void> checkExceptionalClass() async {
// pretend we fetch something
await Future.delayed(Duration(seconds: 1)); // Example async operation
int exceptionalCount = 0;
bool hasExceptional(List<Map<String, dynamic>> list) {
return list.any(
(item) =>
item['is_this_exceptional']?.toString() == '1' &&
item['is_active']?.toString() == '1',
);
}
bool hasExceptionalInFlights(List<Map<String, dynamic>> flights) {
for (var flight in flights) {
if (flight['is_active']?.toString() == '1') {
final trips = flight['trips'];
if (trips is List) {
final exceptionalTrip = trips.any(
(trip) => trip['is_this_exceptional']?.toString() == '1',
);
if (exceptionalTrip) return true;
}
}
}
return false;
}
if (hasExceptionalInFlights(flightList)) exceptionalCount++;
if (hasExceptional(trainList)) exceptionalCount++;
if (hasExceptional(accommodationList)) exceptionalCount++;
setState(() {
hasExceptionalClass = exceptionalCount >= 1;
if (hasExceptionalClass &&
_excepntldescriptionController.text.trim().isEmpty) {
_showExceptionalReasonModal(context);
}
if (!hasExceptionalClass) {
_excepntldescriptionController.text = "";
}
});
print("hasExceptionalClass: $hasExceptionalClass");
}
void handleSubmit() async {
await checkExceptionalClass(); // Do async work first
setState(() {
// await checkExceptionalClass();
print("Handle Submit....after check");
print("Hansle Submit....11");
if (validateForm() && temporaryMessage == null) {
print("Hansle Submit....11222");
// 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}, "
// " ");
print("Start loader at: ${DateTime.now()}");
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const SavingLoader(),
);
postPlanData(planData);
// await postPlanData(planData);
print("Hide loader at: ${DateTime.now()}");
// Close loading dialog (ONLY if still mounted)
Future.delayed(Duration(seconds: 12), () {
if (mounted) {
Navigator.of(context, rootNavigator: true).pop();
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');
}
}
});
// if (mounted) Navigator.of(context, rootNavigator: true).pop();
}
});
}
Future<void> 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
planData['updated_by'] = selfId; // Add plan_id for update
} else {
planData['created_by'] = selfId; // 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',
'app-signature': 'ts-traveltool-2025-signature-123456',
},
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 if (response.statusCode == 403) {
print("403-FORB");
await apiService.logout(context);
return null;
// throw Exception('Failed to load users');
}
else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Trip Creation Failed"),
content: Text(
"There was a problem submitting your plan. Please try again.",
),
actions: [
TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
} 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 FocusTraversalGroup(
policy: OrderedTraversalPolicy(), // 👈 more predictable tab order
descendantsAreFocusable: true,
child: 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: [
//
_buildDescriptionColumn(isDesktop),
SizedBox(width: 25),
if (hasExceptionalClassInUpdate ||
_excepntldescriptionController.text != "")
_buildNonDescriptionColumn(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDescriptionColumn(isDesktop),
SizedBox(height: 15),
if (hasExceptionalClassInUpdate)
_buildNonDescriptionColumn(),
],
),
// 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,
onExeptionalClass: handleExcentionalClass,
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),
// )
// ],
// )
},
),
// replace with your full form column
);
}
// String? getSelectedCostCenterName() {
// if (selectedCostCenterId == null || apiCostData == null) return null;
// return apiCostData!.firstWhere(
// (item) => item['department_id'] == selectedCostCenterId,
// orElse: () => null,
// )?['name'];
// }
final List<Map<String, dynamic>> allTripTypes = [
{"dropdown_key": "1", "dropdown_value": "Domestic"},
{"dropdown_key": "2", "dropdown_value": "International"},
];
List<Map<String, dynamic>> 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<Widget> _buildTripRow(bool isMobile) {
List<dynamic> purposeList = apiData?['plan_is_billable'] ?? [];
List<Map<String, dynamic>> 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,
padding: const EdgeInsets.symmetric(horizontal: 0),
// isFocused: focusStates["trip_typeFocused"] ?? false,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.32
: MediaQuery.of(context).size.width * 0.88,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
// width: double.infinity,
width: double.infinity,
child: Focus(
focusNode: focusNodes["trip_typeFocusNode"],
onFocusChange: (hasFocus) {
setState(() {
focusStates["trip_typeFocused"] = hasFocus;
});
},
child: GestureDetector(
onTap: () {
print("TRIP CLICKESS1");
// Request focus when user taps
focusNodes["trip_typeFocusNode"]?.requestFocus();
print("TRIP CLICKESS15");
},
child: DropdownSearch<Map<String, dynamic>>(
onBeforePopupOpening: (selectedItem) async {
bool hasData = await serviceHasData();
if (hasData) {
bool? shouldProceed = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
"Changing trip type will remove added services",
),
content: Text("Do you wish to proceed?"),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false); // Cancel
},
child: Text("Cancel"),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true); // Continue
},
child: Text("Continue"),
),
],
);
},
);
return shouldProceed ?? false;
}
return true;
},
enabled: !hasUpdateVal,
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: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
// border: InputBorder.none, // No underline
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color:
(focusStates["trip_typeFocused"] ?? false)
? layoutColor!
: Colors.white,
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
(focusStates["trip_typeFocused"] ?? false)
? layoutColor!
: Colors.white,
// : const Color(0xFFD6D5E6),
width: 0.5,
// const Color(0xFFD6D5E6),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: layoutColor!, width: 1),
),
contentPadding: const 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<Map<String, dynamic>>((item) {
// return Map<String, dynamic>.from(item);
// }).toList(),
onChanged:
widget.isViewMode
? null
: (Map<String, dynamic>? newItem) {
final newTripType =
newItem?['dropdown_key'].toString();
print("newTripType - $newTripType");
print("_selectedTripType- $_selectedTripType");
if (newTripType != _selectedTripType) {
print("Type changed");
clearAllServiceLists();
dynamicItineraryKey.currentState
?.clearAllServiceLists();
}
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(
// isFocused: focusStates["is_billableFocused"] ?? false,
isFocused: false,
padding: const EdgeInsets.symmetric(horizontal: 0),
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.2
: null,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
width: double.infinity,
child: Focus(
focusNode: focusNodes["is_billableFocusNode"],
onFocusChange: (hasFocus) {
setState(() {
focusStates["is_billableFocused"] = hasFocus;
});
},
child: GestureDetector(
onTap: () {
// Request focus when user taps
focusNodes["is_billableFocusNode"]?.requestFocus();
},
child: DropdownSearch<Map<String, dynamic>>(
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: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color:
(focusStates["is_billableFocused"] ?? false)
? layoutColor!
: Colors.white,
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
(focusStates["is_billableFocused"] ?? false)
? layoutColor!
: Colors.white,
// : const Color(0xFFD6D5E6),
width: 0.5,
// const Color(0xFFD6D5E6),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: layoutColor!, width: 1),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
),
),
selectedItem: purposeList
.cast<Map<String, dynamic>>()
.firstWhere(
(item) => item['dropdown_key'] == _selectedIsBillable,
orElse: () => {},
),
dropdownButtonProps: const DropdownButtonProps(
icon: Icon(Icons.arrow_drop_down),
),
// itemAsString: (item) => item['dropdown_value'] ?? '',
// items: purposeList.cast<Map<String, dynamic>>(),
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<Map<String, dynamic>>((item) {
return Map<String, dynamic>.from(
item,
); // Ensuring each item is properly cast
}).toList(),
onChanged:
widget.isViewMode
? null
: (Map<String, dynamic>? newItem) {
if (newItem != null) {
setState(() {
_selectedIsBillable =
newItem['dropdown_key'].toString();
});
}
},
),
),
),
),
),
// Row(
// children: purposeList.map<Widget>((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<Widget> _buildCostCenter(bool isDesktop) {
// if (apiData == null) {
// return Center(child: CircularProgressIndicator()); // Show loading indicator
// }
// 'plan_purpose_of_travel' Starts ------------------------------------------------------
// List<dynamic> purposeList = apiData?['plan_purpose_of_travel'] ?? [];
List<Map<String, dynamic>> purposeList = List<Map<String, dynamic>>.from(
apiData?['plan_purpose_of_travel'] ?? [],
);
List<DropdownMenuItem<String>> dropdownItems =
purposeList
.map(
(item) => DropdownMenuItem<String>(
// 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<String>(
// 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<dynamic> funcDeptList = apiData?['plan_functional_department'] ?? [];
List<Map<String, dynamic>> funcDeptList = List<Map<String, dynamic>>.from(
apiData?['plan_functional_department'] ?? [],
);
List<DropdownMenuItem<String>> dropdownFuncDeptItems =
funcDeptList
.map(
(item) => DropdownMenuItem<String>(
// value: item['dropdown_key'],
value: item['dropdown_key']?.toString(),
child: Text(item['dropdown_value']),
),
)
.toList();
if (dropdownFuncDeptItems.isEmpty) {
dropdownFuncDeptItems.add(
DropdownMenuItem<String>(
// 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(
width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null,
// isFocused: focusStates["cost_center_idFocused"] ??
isFocused: false,
padding: const EdgeInsets.symmetric(horizontal: 0),
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
width: double.infinity,
child:
apiCostData == null
? Center(
child: Transform.scale(
scale: 0.5,
child: CircularProgressIndicator(),
),
)
: Focus(
focusNode: focusNodes["cost_center_idFocusNode"],
onFocusChange: (hasFocus) {
setState(() {
focusStates["cost_center_idFocused"] = hasFocus;
});
},
child: GestureDetector(
onTap: () {
// Request focus when user taps
focusNodes["cost_center_idFocusNode"]
?.requestFocus();
},
child: DropdownSearch<String>(
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: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color:
(focusStates["cost_center_idFocused"] ??
false)
? layoutColor!
: Colors.white,
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
(focusStates["cost_center_idFocused"] ??
false)
? layoutColor!
: Colors.white,
// : const Color(0xFFD6D5E6),
width: 0.5,
// const Color(0xFFD6D5E6),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: layoutColor!,
width: 1,
),
),
// 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<String>(
// 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<DropdownMenuItem<String>>((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.15
: MediaQuery.of(context).size.width * 0.88,
// isFocused: focusStates["purpose_of_travelFocused"] ?? false,
isFocused: false,
padding: const EdgeInsets.symmetric(horizontal: 0),
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35, // Set appropriate height
child:
apiData == null
? Center(
child: CircularProgressIndicator(),
) // Show loading inside dropdown
: Focus(
focusNode: focusNodes["purpose_of_travelFocusNode"],
onFocusChange: (hasFocus) {
setState(() {
focusStates["purpose_of_travelFocused"] = hasFocus;
});
},
child: GestureDetector(
onTap: () {
// Request focus when user taps
focusNodes["purpose_of_travelFocusNode"]
?.requestFocus();
},
child: DropdownSearch<Map<String, dynamic>>(
selectedItem: purposeList.firstWhere(
(item) =>
item['dropdown_key'].toString() ==
selectedPurpose,
orElse:
() => <String, dynamic>{}, // ✅ Safe fallback
),
items: purposeList,
itemAsString:
(Map<String, dynamic> 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(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color:
(focusStates["purpose_of_travelFocused"] ??
false)
? layoutColor!
: Colors.white,
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
(focusStates["purpose_of_travelFocused"] ??
false)
? layoutColor!
: Colors.white,
// : const Color(0xFFD6D5E6),
width: 0.5,
// const Color(0xFFD6D5E6),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: layoutColor!,
width: 1,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
),
),
onChanged:
widget.isViewMode
? null
: (Map<String, dynamic>? 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<String>(
// 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: focusStates["functional_departmentFocused"] ?? false,
isFocused: false,
padding: const EdgeInsets.symmetric(horizontal: 0),
isDesktop: widget.isDesktop,
width:
isDesktop
? MediaQuery.of(context).size.width * 0.2
: MediaQuery.of(context).size.width * 0.88,
// 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
: Focus(
focusNode: focusNodes["functional_departmentFocusNode"],
onFocusChange: (hasFocus) {
setState(() {
focusStates["functional_departmentFocused"] =
hasFocus;
});
},
child: GestureDetector(
onTap: () {
// Request focus when user taps
focusNodes["functional_departmentFocusNode"]
?.requestFocus();
},
child: DropdownSearch<Map<String, dynamic>>(
selectedItem: funcDeptList.firstWhere(
(item) =>
item['dropdown_key'].toString() ==
selectedFuncDept,
orElse:
() => <String, dynamic>{}, // ✅ Safe fallback
),
items: funcDeptList,
itemAsString:
(Map<String, dynamic> 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: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color:
(focusStates["functional_departmentFocused"] ??
false)
? layoutColor!
: Colors.white,
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
(focusStates["functional_departmentFocused"] ??
false)
? layoutColor!
: Colors.white,
// : const Color(0xFFD6D5E6),
width: 0.5,
// const Color(0xFFD6D5E6),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: layoutColor!,
width: 1,
),
),
),
),
onChanged:
widget.isViewMode
? null
: (Map<String, dynamic>? newValue) {
dynamic val =
newValue?['dropdown_value']
.toString();
setState(() {
selectedFuncDept =
newValue?['dropdown_key']
.toString();
if (val ==
" Others (Kindly enter SO number )") {
hasSoNumber = true;
} else {
hasSoNumber = false;
_soNumberController.text = "";
}
});
print(
"selectedFuncDept1 - ${newValue?['dropdown_value'].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),
),
),
],
),
SizedBox(width: 25, height: 5),
hasSoNumber
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"SO Number *", // Your label
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["so_numberFocused"] ?? false,
// isFocused: _isdescriptionFocused,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.2
: null,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
child: TextField(
focusNode: focusNodes["so_numberFocusNode"],
controller: _soNumberController,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "SO Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (validationErrors["so_number"] != null)
Padding(
padding: EdgeInsets.only(top: 4),
child: Text(
validationErrors["so_number"]!,
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
),
),
],
)
: SizedBox.shrink(),
];
}
List<Widget> _buildPlanTrip(bool isDesktop) {
List<Map<String, String>> options = [
{"title": "Self", "value": "Option 1"},
{"title": "Other Employee", "value": "Option 2"},
{"title": "Others (Non Employee)", "value": "Option 3"},
];
print("isViewModeYY: ${widget.isViewMode}, hasUpdateVal: $hasUpdateVal");
return [
CustomTextFieldWrapper(
// isFocused: focusStates["trip_plannedFocused"] ?? false,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.32
: MediaQuery.of(context).size.width * 0.88,
isFocused: false,
padding: const EdgeInsets.symmetric(horizontal: 0),
isDesktop: isDesktop,
layoutColor: widget.layoutColor,
child: SizedBox(
height: 35,
width: double.infinity,
child: Focus(
focusNode: focusNodes["trip_plannedFocusNode"],
onFocusChange: (hasFocus) {
setState(() {
focusStates["trip_plannedFocused"] = hasFocus;
});
},
child: GestureDetector(
onTap: () {
// Request focus when user taps
focusNodes["trip_plannedFocusNode"]?.requestFocus();
},
child: DropdownSearch<String>(
selectedItem:
options.firstWhere(
(opt) => opt['value'] == _selectedOption,
)['title'],
enabled: !hasUpdateVal,
// enabled: || !hasUpdateVal,
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: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color:
(focusStates["trip_typeFocused"] ?? false)
? layoutColor!
: Colors.white,
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
(focusStates["trip_plannedFocused"] ?? false)
? layoutColor!
: Colors.white,
// : const Color(0xFFD6D5E6),
width: 0.5,
// const Color(0xFFD6D5E6),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: layoutColor!, width: 1),
),
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(() async {
_selectedOption = selected["value"]!;
if (_selectedOption == "Option 2" ||
_selectedOption == "Option 3") {
_selectedTripType = null;
_showInputDialog(selected["title"]!);
} else if (_selectedOption == "Option 1") {
otherUserName = userName;
await apiService.handleTripWiseToken(selfId!, context);
fetchUserDetails();
_selectedTripType = null;
dynamicItineraryKey.currentState
?.loadOrgSelectedAlServices();
dynamicItineraryKey.currentState
?.updateSelectedServices();
}
});
},
),
),
),
),
),
];
}
void _showExceptionalReasonModal(BuildContext context) {
showDialog(
context: context,
barrierDismissible: false, // prevent closing by tapping outside
builder: (context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Container(
padding: const EdgeInsets.all(16),
// decoration: BoxDecoration(
// border: Border.all(
// color: Colors.grey, // or any color you prefer
// width: 1.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
color: Colors.white,
width: 400,
// width: MediaQuery.of(context).size.width * 0.5,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Reason for travel policy exception",
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
TextField(
controller: _excepntldescriptionController,
maxLines: 3,
decoration: InputDecoration(
hintText: "Enter reason...",
hintStyle: GoogleFonts.poppins(fontSize: 10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
10,
), // 👈 Rounded border
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: layoutColor!), // optional
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: layoutColor!), // optional
),
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // Cancel
},
child: Text(
"Cancel",
style: GoogleFonts.poppins(fontSize: 12),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
// Use the entered text
String reason =
_excepntldescriptionController.text.trim();
if (reason.isNotEmpty) {
print("Reason entered: $reason");
setState(() {
hasExceptionalClassInUpdate = true;
validationErrors.remove("exceptional_plan_reason");
});
Navigator.of(context).pop(); // Close dialog
} else {
// Optional: show a validation message
}
},
child: Text(
"OK",
style: GoogleFonts.poppins(fontSize: 12),
),
),
],
),
],
),
),
);
},
);
}
Widget _buildNonDescriptionColumn() {
return Column(
children: [
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Reason for travel policy exception*", // Your label
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["excepntldescriptionFocused"] ?? false,
// isFocused: _isdescriptionFocused,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.42
: null,
isDesktop: widget.isDesktop,
hasError: validationErrors["exceptional_plan_reason"] != null,
child: SizedBox(
height: 55,
child: TextField(
focusNode: focusNodes["excepntldescriptionFocusNode"],
controller: _excepntldescriptionController,
maxLines: 2,
keyboardType: TextInputType.multiline,
onChanged: (value) {
validationErrors.remove("exceptional_plan_reason");
},
style: TextStyle(fontSize: 12),
enabled: !widget.isViewMode,
decoration: InputDecoration(
labelText: "Reason for exception ",
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),
),
),
),
),
if (validationErrors["exceptional_plan_reason"] != null)
Padding(
padding: EdgeInsets.only(top: 4),
child: Text(
validationErrors["exceptional_plan_reason"]!,
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.red,
),
),
),
],
),
],
),
],
);
}
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: focusStates["descriptionFocused"] ?? false,
// isFocused: _isdescriptionFocused,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.32
: MediaQuery.of(context).size.width * 0.88,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 55,
child: TextField(
focusNode: focusNodes["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<Widget> _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 _buildTN(bool isDesktop) {
return SizedBox(
width: isDesktop ? MediaQuery.of(context).size.width * 0.7443333 : 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.w400,
color: Colors.grey,
// 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 _buildTripName(bool isDesktop) {
return SizedBox(
width: isDesktop ? MediaQuery.of(context).size.width * 0.5 : 180,
height: 35,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
reverse: false, // show right-end on overflow
physics: BouncingScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: isDesktop ? MediaQuery.of(context).size.width * 0.5 : 180,
),
child: IntrinsicWidth(
child: TextField(
controller: _tripTitleController,
maxLines: 1,
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.w400,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
vertical: 16,
),
errorText: validationErrors["trip_title"],
errorStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.red,
),
),
keyboardType: TextInputType.text,
scrollPhysics: BouncingScrollPhysics(),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_ ]')),
],
),
),
),
),
);
}
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<Widget> _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 && (widget.approverStatus == "Approval pending"))
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']);
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const SavingLoader(),
);
} 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<Widget> _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) async {
setState(() {
otherUserName = input;
selectedplanUserId = userId;
selectedIstravelUser = isTraveller;
});
print("USer entered : $otherUserName $userId $isTraveller");
getSelectedPlanFor();
if (isTraveller) {
await apiService.handleTripWiseToken(
selfId!,
context,
); // since userId is for traveller
} else {
await apiService.handleTripWiseToken(
userId,
context,
); // fallback to selfId
}
fetchUsrDtlFromSelectedTripUser();
dynamicItineraryKey.currentState?.loadOrgSelectedAlServices();
dynamicItineraryKey.currentState?.updateSelectedServices();
},
onClose: () async {
print("Choosede Clsoes");
await apiService.handleTripWiseToken(selfId!, context);
fetchUserDetails();
},
layoutColorForUser: widget.layoutColor!,
currentUser: selfId,
);
},
);
}
}